Yes Second Life

セカンドライフに早9年、現役セカンドライファーの考えてること

セカンドライフ Animesh 応用編

この記事はセカンドライフ技術系アドベントカレンダー2019向けの記事です。

セカンドライフ技術系 Advent Calendar 2019 - Adventar

前回の基本編でオブジェクトをアニメーションさせれるようになったのですが、あのままではずっとその場で再生するだけです。セカンドライフにはPathfindingというオブジェクトをNPCのように動かす機能があります。今回はそれと組み合わせることで、NPCのように動作するAnimeshを作成してみます。

Pathfindingとは

Pathfindingとは、オブジェクトをNPCのように動作させる仕組みです。オブジェクトに以下のような動作をさせられます。

目的地へ移動 llNavigateTo
追跡 llPursue
逃走 llEvade
離れる llFleeFrom
巡回 llPatrolPoints
徘徊 llWanderWithin

AnimeshとPathfindingを組み合わせる

今回は追跡のPathfindingとAnimeshを組み合わせて、自分についてくるNPCを作ってみます。ずっと歩くアニメーションを再生していると自分が止まったときにも歩き続けて不自然なので、Timerで自分の速度を監視して一定の速度以下になったらアニメーションを停止するようにしてみます。

// 歩きのアニメーション名
string ANIM_NAME = "walk";

default
{
	state_entry()
	{
		// このオブジェクトをPathfindingのキャラクターに設定、追跡速度は2.0
		llCreateCharacter([CHARACTER_DESIRED_SPEED, 2.0]);

		// オブジェクトオーナーの追跡を開始
		llPursue(llGetOwner(), []);

		// 0.25秒ごとにタイマーイベント設定
		llSetTimerEvent(0.25);
	}

	on_rez(integer start_param)
	{
		// Rez時にリセット
		llResetScript();
	}

	timer()
	{
		// 一定の速度以下の場合
		if(llVecMag(llGetVel()) < 0.1)
		{
			// 歩きのアニメーションを停止する
			llStopObjectAnimation(ANIM_NAME);
		}
		// 一定の速度以上の場合
		else
		{
			// 歩きのアニメーションを実行する
			llStartObjectAnimation(ANIM_NAME);
		}
	}
}

アニメーションを実行中にllStartObjectAnimationしたり、停止中にllStopObjectAnimationしてもエラーはでなかったので、チェックはなしでシンプルに実装しました。実際の動作は動画でどうぞ( ̄∇  ̄ )

AnimeshをPathfindingと組み合わせることで、かなり本格的なNPCがつくれます。単についてくるだけじゃなくてウロウロさせたり逃走させたり色々できるので可能性無限大ですね( ̄∇  ̄ )

セカンドライフ Animesh 基本編

この記事はセカンドライフ技術系アドベントカレンダー2019向けの記事です。

セカンドライフ技術系 Advent Calendar 2019 - Adventar

アバターでなくオブジェクトをアニメーションさせるAnimeshという機能があるんですが、まとまった日本語情報がなかったので今回紹介しようとおもいます。

Animeshとは

以前から骨組みが入ったメッシュであるリグドメッシュをアバターとして使うことができたんですが、それをオブジェクトとして配置してアニメーションさせる機能がAnimeshです。

今まではオブジェクトはスカルプアニメくらいしかなかったのですが、本格的なアニメーションをつけることができるようになったので、NPC的な使い方をしたり応用次第で色々な演出ができます。

Animeshの関数

Animesh関連の関数は3つしかありません。

llStartObjectAnimation

引数で渡された名前のアニメーションをインベントリから探して再生します。

llStopObjectAnimation

引数で渡された名前のアニメーションを停止します。

llGetObjectAnimationNames

現在再生されているすべてのアニメーションをリストで返します。

たとえばこれらを組み合わせて、すべてのアニメーションを停止する関数とかつくれます。

stopAllAnimation()
{
    list animList = llGetObjectAnimationNames();
    integer length = llGetListLength(animList);
    integer i = 0;

    while (i < length)
    {
        string anim = llList2String(animList, i);
        llStopObjectAnimation(anim);
        i++;
    }
}

試してみるには

昔作ったリグドメッシュで試してみたんですが、なぜかうまくいきませんでした。セカンドライフにはAnimeshを試すためのサンドボックスが用意されているので、とりあえずそこに行ってみるとAnimesh対応のリグドメッシュが配布されていました。
Second Life Maps | Animesh1

f:id:sabro:20191201002829p:plain
Rezすると勝手に動き出しますがタッチすると停止します。停止させてからオブジェクトの中身を削除すればお試し用のまっさらなリグドメッシュをゲットできます。

スクリプトサンプル

とりあえず、簡単なスクリプトを書いてみました。

integer isPlaying = FALSE;

default
{
    state_entry()
    {
        llStopObjectAnimation("walk");
    }
    
    touch_start(integer total_number)
    {
        if(isPlaying)
        {
            llStopObjectAnimation("walk");
            isPlaying = FALSE;
        }
        else
        {
            llStartObjectAnimation("walk");
            isPlaying = TRUE;
        }
    }
}


はっきりいって、すごく簡単ですね。リグドメッシュさえあれば、誰でもアニメーションするオブジェクトをつくれます。人がいなくて寂しいシムにも動きをだせるんじゃないでしょうか( ̄∇  ̄ )

セカンドライフビューワ改造への道 - 改造編

この記事はセカンドライフ技術系アドベントカレンダー向けの記事です。

セカンドライフ技術系 Advent Calendar 2018 - Adventar

前回環境を構築したので、今回はいよいよ改造していきます。

で、どういう改造をするかなんですが、セカンドライフはたいていのことは内部スクリプトのLSLで実現できてしまうので かなり悩みました。最終的にLSLではビューワのメニューの拡張はできないので、右クリックメニューの拡張をすることにしました。

右クリックメニューに「Jsonへ出力」を追加し、オブジェクトの情報をJson形式で出力できるようにします。 メニューの拡張についてはWikiにドキュメントがあるので、ここを参考に進めていきます。

Adding a menu item - Second Life Wiki

f:id:sabro:20181225032329p:plain

XUI設定ファイルの編集

セカンドライフビューワではXUIというUIフレームワークが実装されていて、XMLに設定を書くことでUIを拡張できます。 メニューの拡張もXUIの設定で行います。 その設定XMLがどこにあるかを知るには、ソース全体のフォルダ構成を理解するのが早道です。

f:id:sabro:20181225032502p:plain

ソースコードのトップフォルダ、indraの配下には「llui」「llxml」のようにllで名前が始まるフォルダがたくさんあります。 これらがビューワの基礎ライブラリです。そしてそれらのライブラリを使用するビューワ本体のコードは「newview」配下に 格納されています。

XUIの設定XMLは「newview」フォルダ直下の「skins」フォルダにあります。右クリックメニューを追加するには 「indra\newview\skins\default\xui\en\menu_object.xml」ファイルを編集します。 menu_item_call.on_clickタグは、クリックされた際に呼ばれるイベントハンドラの設定を行っています。

indra\newview\skins\default\xui\en\menu_object.xml

  <menu_item_call
      enabled="false"
      label="Delete"
      name="Delete">
    <menu_item_call.on_click
        function="Object.Delete" />
    <menu_item_call.on_enable
        function="Object.EnableDelete" />
  </menu_item_call>
  <!-- Json出力メニューを追加 ↓↓↓ -->
  <menu_item_call
      enabled="true"
      label="ToJson"
      name="ToJson">
    <menu_item_call.on_click
        function="Object.ToJson" />
  </menu_item_call>
  <!-- Json出力メニューを追加 ↑↑↑ -->
  <menu_item_separator
       layout="topleft" />

メニューの日本語化をする場合は、jaフォルダの方のmenu_object.xmlを編集します。

indra\newview\skins\default\xui\ja\menu_object.xml

 <menu_item_call label="買う" name="Buy..."/>
    <menu_item_call label="削除" name="Delete"/>
  <!-- Json出力メニューを追加 ↓↓↓ -->
    <menu_item_call label="Jsonへ出力" name="ToJson"/>
  <!-- Json出力メニューを追加 ↑↑↑ -->
    <menu_item_call label="パーティクル所有者をブロック" name="Mute Particle"/>

イベントハンドラの実装

メニューの拡張はできたので、次はイベントハンドラを書いていきます。 メニューのイベントハンドラは、「newview」フォルダ直下の「llviewermenu.cpp」に書く決まりになっています。

indra\newview\llviewermenu.cpp

#include "llnotificationmanager.h"  // includeを追加
...
...
// Json出力のイベントハンドラ
void handle_object_tojson()
{
    // オブジェクトを選択していない場合はリターン
    if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) return;

    // 選択中のオブジェクトを取得
    LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();
    
    // 選択中オブジェクトのルートオブジェクトを取得
    LLViewerObject* selected_objectp = selection->getFirstRootObject();

    // 選択しているオブジェクトがある場合はJson出力
    if (selected_objectp)
    {
        // Json文字列を構築
        std::ostringstream oss;
        oss << "{" << std::endl;
        oss << "    \"uuid\" : \"" << selected_objectp->getID().asString() << "\"" << std::endl;
        oss << "    \"position\" : \"" << selected_objectp->getPosition() << "\"" << std::endl;
        oss << "    \"scale\" : \"" << selected_objectp->getScale() << "\"" << std::endl;
        oss << "    \"rotation\" : \"" << selected_objectp->getRotation() << "\"" << std::endl;
        oss << "    \"permmodify\" : \"" << selected_objectp->permModify() << "\"" << std::endl;
        oss << "    \"permcopy\" : \"" << selected_objectp->permCopy() << "\"" << std::endl;
        oss << "    \"permmove\" : \"" << selected_objectp->permMove() << "\"" << std::endl;
        oss << "    \"numfacees\" : \"" << selected_objectp->getNumFaces() << "\"" << std::endl;
        oss << "    \"ismesh\" : \"" << selected_objectp->isMesh() << "\"" << std::endl;
        oss << "}";

        // LLChatクラスを作成し、Json文字列を設定
        LLChat chat;
        chat.mSourceType = CHAT_SOURCE_SYSTEM;  // システムからのチャットに設定
        chat.mText = oss.str();

        // LLSDクラス作成、関数の引数に渡すだけなので空でOK
        LLSD args;

        // チャット出力
        LLNotificationsUI::LLNotificationManager::instance().onChat(chat, args);
    }
}

最後にXUIのメニューとイベントハンドラを関連付けるコードを書きます。 「llviewermenu.cpp」の下部に右クリックメニューの関連付け処理が書かれている箇所があるので そこに追加しておきます。

indra\newview\llviewermenu.cpp

 // Object pie menu
    view_listener_t::addMenu(new LLObjectBuild(), "Object.Build");
    commit.add("Object.Touch", boost::bind(&handle_object_touch));
    commit.add("Object.SitOrStand", boost::bind(&handle_object_sit_or_stand));
    commit.add("Object.Delete", boost::bind(&handle_object_delete));
    view_listener_t::addMenu(new LLObjectAttachToAvatar(true), "Object.AttachToAvatar");
    view_listener_t::addMenu(new LLObjectAttachToAvatar(false), "Object.AttachAddToAvatar");
    view_listener_t::addMenu(new LLObjectReturn(), "Object.Return");
    commit.add("Object.Duplicate", boost::bind(&LLSelectMgr::duplicate, LLSelectMgr::getInstance()));
    view_listener_t::addMenu(new LLObjectReportAbuse(), "Object.ReportAbuse");
    view_listener_t::addMenu(new LLObjectMute(), "Object.Mute");

    enable.add("Object.VisibleTake", boost::bind(&visible_take_object));
    enable.add("Object.VisibleBuy", boost::bind(&visible_buy_object));

    commit.add("Object.Buy", boost::bind(&handle_buy));
    commit.add("Object.Edit", boost::bind(&handle_object_edit));
    commit.add("Object.Inspect", boost::bind(&handle_object_inspect));
    commit.add("Object.Open", boost::bind(&handle_object_open));
    commit.add("Object.Take", boost::bind(&handle_take));
    commit.add("Object.ShowInspector", boost::bind(&handle_object_show_inspector));
    enable.add("Object.EnableOpen", boost::bind(&enable_object_open));
    enable.add("Object.EnableTouch", boost::bind(&enable_object_touch, _1));
    enable.add("Object.EnableDelete", boost::bind(&enable_object_delete));
    enable.add("Object.EnableWear", boost::bind(&object_selected_and_point_valid));

    enable.add("Object.EnableStandUp", boost::bind(&enable_object_stand_up));
    enable.add("Object.EnableSit", boost::bind(&enable_object_sit, _1));

    view_listener_t::addMenu(new LLObjectEnableReturn(), "Object.EnableReturn");
    enable.add("Object.EnableDuplicate", boost::bind(&LLSelectMgr::canDuplicate, LLSelectMgr::getInstance()));
    view_listener_t::addMenu(new LLObjectEnableReportAbuse(), "Object.EnableReportAbuse");

    enable.add("Avatar.EnableMute", boost::bind(&enable_object_mute));
    enable.add("Object.EnableMute", boost::bind(&enable_object_mute));
    enable.add("Object.EnableUnmute", boost::bind(&enable_object_unmute));
    enable.add("Object.EnableBuy", boost::bind(&enable_buy_object));
    commit.add("Object.ZoomIn", boost::bind(&handle_look_at_selection, "zoom"));
    // この行を追加 ↓↓↓
    commit.add("Object.ToJson", boost::bind(&handle_object_tojson));

あとはVisualStudio上で「secondlife-bin」プロジェクトをビルドするとビルドフォルダ配下に セカンドライフの実行ファイルができます。

f:id:sabro:20181225043344p:plain

実際、起動してみるとこんな感じで出力されました。

f:id:sabro:20181225035625p:plain

今回、C++をほぼ初めて実践的に使ってみましたがなんとかビューワの改造ができてほっとしました。 アドベントカレンダーのネタということでオレオレ改造みたいな感じになりましたが、 本来はビューワをいじるならJiraを参照して挙がっているissueに対応するのがいいと思います。 腕に覚えのある方は挑戦してみてください( ̄∇  ̄ )

セカンドライフビューワ改造への道 - 開発環境構築編

この記事はセカンドライフ技術系アドベントカレンダー2018向けの記事です。

セカンドライフ技術系 Advent Calendar 2018 - Adventar

最近C++ができるようになってきたので今回はセカンドライフビューワの改造に挑戦。まずはVisualStudioで開発環境を整えるところまでやっていきます。ちなみに今は64bit版のビューワもありますが、とりあえず32bit版を改造します。

もろもろインストール

少し情報が古いですが、Visual Studio 2013 Viewer Builds - Second Life Wikiを参考にすると、色々インストールする必要があるみたいです。

あまり、環境を汚したくなかったので仮想マシン上で開発していくことにします。以下のリンクから有効期限付きの評価版Windows10のVMがダウンロードできます。

Windows 10 仮想マシンをダウンロードする - Windows アプリ開発

f:id:sabro:20181203004654p:plain

このVMには最初からVisualStudio2017、Windows Subsystem for Linuxがインストールされています。VMを立ち上げたら、まずは色々インストールしていきましょう。

Visual Studio 2013

VMにはVisualStudio2017が入っているのですが、なぜかビルドが通らず・・。ここで2,3日費やした後、2017はアンインストールしWikiの説明にあるVisualStudio2013をインストールしたらあっさりビルドできました。2013は、無料のVisual Studio Dev Essentialsプログラムに参加した後、以下のリンクからダウンロードできました。

以前の Visual Studio ソフトウェアのダウンロード | Visual Studio - Visual Studio

DirectX SDK

以下のリンクからインストーラをダウンロードし、管理者権限で実行。HeaderとLib以外は不要なのでチェックをはずします。

Download DirectX Software Development Kit from Official Microsoft Download Center

f:id:sabro:20181203004424p:plain

CMake

以下のリンクからインストーラをダウンロード。32bit版でないといけないので注意です。インストールオプションで"Add CMake to the system PATH for all users"を選択します。

Download | CMake

f:id:sabro:20181203004502p:plain

Cygwin

Windows Subsystem for Linuxが入っているので不要かと思ったのですが、ビルド時にパスをCygwin向けに変換しているところがあって、やっぱり駄目でした。Cygwinは以下のリンクからダウンロード。こちらは64bit版を選択します。インストーラを管理者権限で実行し、パッケージ選択画面では、「Devel/patch」を追加します。

Cygwin Installation

f:id:sabro:20181203004539p:plain

Python

実はVMには最初からPython3が入っているのですが、ビルドに使用するのは2系のようなのでPython3はアンインストールし、Python2の最新を入れました。こちらは32bit版でないといけないので注意です。インストールオプションでは"Add python.exe to Path"を"Will be installed on local hard drive"に設定します。

Download Python | Python.org

f:id:sabro:20181203004622p:plain

Mercurial

ビューワのソースコードはBitbacketで管理されているのでMercurialで取得します。コマンドラインでhgが使えればいいので、TortoiseHgは不要です。

Download - Mercurial

Autobuild

 AutobuildはPythonで書かれています。Pythonのパッケージ管理ツールpipを使ってインストールします。Windowsコマンドプロンプトで以下を入力すればOKです。

pip install hg+http://bitbucket.org/lindenlab/autobuild-1.1#egg=autobuild

ビルド変数の設定

ここからは、Cygwinコンソール上での作業になります。さて、ビューワのソースコードとは別にビルド変数を設定するツールが用意されています。

lindenlab / viewer-build-variables — Bitbucket

上記リンクからツールをダウンロードして解凍し、その中にある「variables」ファイルのパスを環境変数に設定します。Cygwin形式ではなくWindows形式のパスで指定する必要があることに注意。ここで結構ハマりました・・・。

export AUTOBUILD_VARIABLES_FILE="C:\Users\user\dev\lindenlab-viewer-build-variables\variables"

ビューワのビルド 

作業ディレクトリに移動して、以下のコマンドでビューワのソースコードを取得します。

hg clone https://bitbucket.org/lindenlab/viewer-release

Cloneが終わったらトップフォルダに移動し、autobuildで環境変数の設定をします。

cd viewer-release
eval "$(autobuild source_environment)"

次はautobuildでコンフィグレーションを実行します。これが完了すると、ソリューションファイルが作成されます。「RelWithDebInfoOS」はビルド構成です。Debugビルドはうまく動かないみたいなのでこれにしました。「32」は32bitの指定です。

autobuild configure -c RelWithDebInfoOS -A 32

これでビルドの準備が整いました。作成されたソリューションファイルでプロジェクトを開いてビルドするか、もしくは以下のコマンドでビルドできます。

autobuild build --no-configure  -c  RelWithDebInfo -A 32

これで開発環境の構築は完了です。ハマりまくってまだ環境構築しかできてないので実際の改造はこれからやります( ̄□  ̄ ||

C++、DirectXを勉強しています

長らくVR世界に向けたWebサービスを作るというスタイルでやってきたのですが、そのやり方にも限界を感じてきたのでC++DirectXなどを勉強しています。

色んな人に今勉強するならUnityやUnrealEngineがいいよと進められましが、背後で動いている仕組みを知っておきたかったのと、GPUの使い方を分かっとくことで、AIやビッグデータなんかにも応用できるなと思ってDirectXにしました。とにかく書籍が少ないのが難点ですが・・・。

VRの波が来るのを待ってるうちに、自分もずいぶん歳をとりましたが、せっかくなんで、なにかひとつくらいはリリースできたらなとおもいます。

仮想世界のアバターデザインについて

これは、セカンドライフ非技術系アドベントカレンダー2017向けに書かれた記事です。

adventar.orgセカンドライフアバターといえばリアル調のアバターだと思われがちですが、自分のアバターはこんな感じです。

f:id:sabro:20171219223946p:plain

日本人には、リアルなアバターよりこういうアバターのほうがいいという人も結構いるんじゃないでしょうか。

こういったアニメ調のアバターを基本とするメタバースがあれば、日本人ももっと仮想世界に来るのにと思っていた時期もあったんですが、最近は考え方が変わってきました。

おそらくそういう仮想世界は米国の人はほとんどプレイしないと思います。米国ではアニメは子供のものという認識が強くて大人は一部をのぞいてリアルなアバターを求めています。つまり、アニメ調アバターの仮想世界は日本やアジアなんかに閉じた世界になります。それはもう3D版のインターネットと言えるメタバースではないものになってしまうんじゃないでしょうか。

というわけで、リアルなアバターを基本としつつも、ぼくのアバターみたいにかわいいものも作れる今のセカンドライフは、実は落とし所としてはイイ線いってるんじゃないかと思います。

もちろん技術が進歩すれば、昔から話題にあがっているマルチバースみたいな世界になり、異なった世界観のメタバースを行き来できたりもするかもしれません。将来的に、異なる文化を持つ人々がメタバースでどういう共存、住み分けをしていくのか考えてみるのも面白いと思います。

HighFidelityのTablet UI Frameworkを使う

この記事は、セカンドライフ技術系アドベントカレンダー2017向けの記事です。

adventar.org

前回ちょろっと触れましたが、HightFidelityでHUDみたいなUIを作るには、Tablet UI Frameworkというのを使います。今回はこのフレームワークで出来ることを見ていきます。

Webページを表示する

Tabletオブジェクトの、gotoWebScreen関数を使うと、指定したWebページを表示できます。

(function() {

    // クリックイベント
    this.clickDownOnEntity = function(entityID, mouseEvent) {

        // Tabletオブジェクトを取得
        var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
    
        // このブログを表示
        tablet.gotoWebScreen("http://sabro.hatenablog.com/");
    };
})

モバイル用のページが表示されるみたいです。

f:id:sabro:20171203232010p:plain

上のスクリプトではTabletオブジェクトを唐突に取得しています。このオブジェクトの定義がないかHighFidelityのインストールディレクトリのJSファイルを色々検索して調べてみたのですが、定義されてる箇所が見つかりませんでした。とりあえず、Tablet UI Frameworkを使用するときのおまじないみたいなものと思っておくことにします。

f:id:sabro:20171203235114p:plain

なお、Webページ以外にもQt向けの宣言型UI言語、QMLの表示にも対応しているみたいですが、今回はそこまで試せませんでした( ̄□  ̄ ||

デフォルトのメニューを拡張する

HighFidelityの世界にログインすると、デフォルトでこんなメニューが表示されます。これは公式で用意されているメニューなわけですが、Tablet UI Frameworkを使えばここに自分の機能を追加することが出来ます。

f:id:sabro:20171204000946p:plain

具体的にはTabletオブジェクトのaddButton関数でメニューUIにボタンを追加し、そのボタンのクリックイベントを定義してやればOKです。スクリプト終了時にはボタンを削除する処理も忘れずに入れておきます。ここでは、ボタンを押すとGoogle検索を呼び出すだけのGoogleボタンを作ってみます。

(function() {
    
    // Tabletオブジェクトを取得
    var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");

    // デフォルトのメニューにボタンを追加、ボタンの文言はGoogleを指定
    // iconプロパティを設定すれば独自のアイコンの表示も可能だがここでは省略
    var button = tablet.addButton({
        text: "Google"
    });

    // メニューのボタンを押した際に呼び出す関数、ここではGoogle検索を表示
    function onClicked() {
        tablet.gotoWebScreen("https://www.google.co.jp/");
    }

    // メニューボタンクリックイベントと関数を接続する
    button.clicked.connect(onClicked);

    // スクリプト終了時に呼び出す関数
    function cleanup() {
        tablet.removeButton(button);
    }

    // スクリプト終了イベントと関数を接続する
    Script.scriptEnding.connect(cleanup);
}()); 

作ったスクリプトはインターフェーススクリプトとして読み込みます。メニューの拡張みたいにクライアントから接続している間だけ有効にしたい機能はインターフェーススクリプトとして使用するのがベストです。作ったスクリプトtablet-test.jsと名前をつけてディスクから読み込みます。

f:id:sabro:20171209201332p:plain

メニューにGoogleボタンが追加されました。

f:id:sabro:20171209201404p:plain

押すとGoogle検索画面が表示されます。

f:id:sabro:20171209205401p:plain

終了処理を仕込んであるのでスクリプトを✕ボタンで停止するとボタンもメニューから消えます。

f:id:sabro:20171209215213p:plain

一通り見てきましたが、公式のメニューを自分で拡張できるのが面白いですね。この拡張性の高さがHighFidelityの魅力でもあります( ̄∇  ̄ )