ベネチア SIM のイベントで広告看板をさげた飛行船 Airship Go Round Signboard を飛ばすコントロールボックス/箱の使い方をまとめました。
- イベント会場の近くに箱を置く
- 箱に広告画像を貼る
基本これだけで OK!
…詳しく書いたページです:
Airship Go Round Signboard Box: How to Use
ベネチア SIM のイベントで広告看板をさげた飛行船 Airship Go Round Signboard を飛ばすコントロールボックス/箱の使い方をまとめました。
基本これだけで OK!
…詳しく書いたページです:
Airship Go Round Signboard Box: How to Use
看板飛行船を自動で出す箱ですが 時間などコントロール情報はオブジェクトの説明のところに書いてあって変更するときはこの説明を書き直します。
ただ編集モードに慣れてないとどこを変えればいいのか迷ったりもするので もっと簡単にタッチでメニューを出してボタンで調整できるようにしました。
実際に変更しなくても または変更する前に 現在の状態をメニューのメッセージ部分で確認することもできます。
メニューのメッセージに現在の状態を表示するスクリプト:
list GoroundMsg = [ // もとのメッセージ
"Going Round: %%", // "%%"部分を数値で置き換える
"Position: %%, Speed: %%",
"Adjust End Time, Position, or Speed:"
];
string setMenuMsg(list msgs,list replaces) {
string msg = "\n"+llDumpList2String(msgs,"\n"); // 改行でつなぐ
list context = llParseString2List(msg,["%%"],[]); // 置き換わる"%%"で分解
return mergeList2String(context,replaces); // 置き換えるリストと混ぜる
}
list getDesc2List() {
string desc = llGetObjectDesc(); // オブジェクトの説明
return llParseString2List(desc,[" "],[]); // 説明を空白で分解
}
string mergeList2String(list context,list replaces) { // 2つのリストを混ぜる
integer n = llGetListLength(context);
integer i = 0;
string merged = "";
for (; i<n; ++i) merged += llList2String(context,i)+llList2String(replaces,i);
return merged; // 混ぜた文字列
}
メッセージ文の流れは決めておいて箇所箇所を数値データと置き換えます。
でその置き換わる数値データは箱オブジェクトの説明からとるわけです。
イベントの看板はたいてい正方形の画面です。
でも正方形でなくてももちろんOKで 今度のイベント(16日木曜日)では縦が長い看板です。
そこでどんな縦横比の看板でも対応できるようにしました。
看板飛行船の準備は箱を編集して画像を貼ることで(Edit > Texture > Texture)できますが もし看板画面が正方形でない場合には箱の横幅も変えます。(Edit > Object > Size:X)
こうすると飛行船が飛ぶときにさげる看板の縦横比を箱と同じにします。
画面縦横の比率だけが問題なので横幅(X)さえ変えれば箱の高さ(Z)は変えなくてOK!
(…高さを変えると箱の一部が地面に埋まったり逆に浮いたりするので あれ?調整が必要?と悩むはめに…(^^;)…実際にはもちろんこの心配も不要!)
看板の縦横比を調節するスクリプト:
listen(integer ch,string na,key id,string msg)
{
list info = llParseString2List(msg,[":"],[]);
string command = llList2String(info,0);
string data = llList2String(info,1);
if (command=="XzRatio") { // 縦横比
float xzratio = (float)data;
vector size = getSignParams(PRIM_SIZE); // 看板のサイズ
vector pos = getSignParams(PRIM_POS_LOCAL); // ローカル位置
pos.z += size.z*0.5; // 看板の上端
size.x = llSqrt(size.x*size.z*xzratio); // 画面の横幅, 面積は同じ
size.z = size.x/xzratio; // 画面の高さ
pos.z -= size.z*0.5; // 看板の中心
llSetLinkPrimitiveParams(LinkSign,[PRIM_POS_LOCAL,pos,PRIM_SIZE,size]);
}
}
箱も看板も Xが横で Zが縦です。
スクリプトはイベントが始まる飛行船を出すタイミングで箱の縦横比を調べて Rez した飛行船に伝えます。
飛行船の側ではもとからつけてる看板と面積は同じになるように縦横の長さを変えます。
さげる位置も看板のてっぺんが同じになるように Z 方向の位置を調節しています。
ベネチアSIM のイベントのとき いつも宣伝看板をさげた飛行船を飛ばしていますが 何度かその時間に来れないことがあったので 事前に用意しておけばその時間に自動で飛ばせるようにしました。
やりかたは:
開始と終了時刻はほとんど 22:00-24:00(JST) なのであらかじめ箱の説明のところに書いてあります。
もしも違う時間に開催されるならこれを書き直す(Edit > General > Description)だけでOK!
準備は超簡単なので誰でもできそうです。
イベントスタッフの人に渡しておけば当日 lovemax がいなくても飛行船が飛ばせるのでこれはサボれる!
飛行船を出したり消したりするきっかけを決めるスクリプトはこちら:
integer isNowArrive(integer time)
{ // TRUE: 指定時刻 time <= now < time+Margin に今到達した?
integer now = getNowMin();
if (time <= now && now < time+Margin) return TRUE;
else return FALSE;
}
integer getNowMin()
{ // time: "2022-06-09T00:01:43..." UTC → return 541 JST(+9:00)
string time = llGetTimestamp(); // UTC 協定世界時
list info = llParseString2List(time,["T"],[]); // 日付と時刻を分ける
time = llList2String(info,1); // UTC
integer now = time2Min(time) + OffsetMin; // 分 JST 日本標準時
if (28*60 <= now) return now - 24*60; // 28時以降は早朝 4時〜
else return now;
}
integer time2Min(string time)
{ // time: "01:15:58" → return 75 分; 秒は無視
list info = llParseString2List(time,[":"],[]);
integer hour = (integer)llList2String(info,0);
integer min = (integer)llList2String(info,1);
if (hour<0) min *= -1; // 時間がマイナスのときは分もマイナス
return 60 * hour + min;
}
時刻は全部分に換算して比較してます。
現在時刻は llGetTimestamp() で UTC (wikipedia 協定世界時) を調べて 時 x 60 + 分 に JST (wikipedia 日本標準時) オフセットの +9 x 60 を足します。
指定する時刻が真夜中以降の場合は 例えば正規の書き方 "00:30" ではなく "24:30" のように前日から続く深夜の書き方で前後を比べやすくしてます。
(…でも 28時以降はさすがに次の日の早朝 4時〜扱いに…)
SLでいろいろ楽しいパーティクルの視覚効果ですがスクリプトで書こうとするとなんだかとても大変そうな感じです。
設定する項目や数字がいろいろあってしかもそれぞれ意味がよくわかりません。
そこでこれ以上簡単にはできない?という究極の超簡単なパーティクルというのを考えてみました。
イベントのとき会場上空を回る看板つき飛行船に仕込んだ ぽこぽこ白いのが飛び出してふわ〜っと漂うパーティクルです。
スクリプトはこちら:
default {
state_entry() {
// 最初はパーティクルを出さない
llParticleSystem([]);
// ここで超簡単なパーティクルの設定
Particle = [
PSYS_PART_FLAGS, PSYS_PART_WIND_MASK // 風に漂う
| PSYS_PART_EMISSIVE_MASK, // 自分で光る(周りの光の影響を受けない)
PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE_CONE, // 発生方向の角度を設定
PSYS_SRC_ANGLE_BEGIN, 0.3*PI // 角度: 0.3x180=54°
];
}
link_message(integer n,integer i,string msg,key id) {
// リンクメッセージで開始または終了
if ((float)msg) llSetTimerEvent(1);
else llResetScript();
}
timer() { // ランダムな時間間隔でパーティクルを出したり止めたり
llSetTimerEvent(MinTime+llFrand(MaxTime-MinTime));
Emit = !Emit;
if (Emit) llParticleSystem(Particle);
else llParticleSystem([]);
}
}
ほんの数行の簡単設定でもとりあえずパーティクルが出るのはデフォルト設定があるからですね。
超簡単では物足りない人向けには LSLポータルのページに詳しい説明がどっさり書いてあります。ただしデフォルトについては書いてないみたいです。(llParticleSystem)
ビューワーの Firestorm のメニューに Build > Object > Edit Particles というのがあって オブジェクト編集中ならそこから発生するパーティクルを編集できる画面が開きます。(つい最近友達に教えてもらいました。知らなかった〜)
この画面に最初から出ている項目や数値がデフォルトなのでしょう。
編集画面の [Copy] ボタンを押せば設定された項目とか数値がクリップボードにコピーされます。
[Inject] ボタンはその編集中のオブジェクトの中に New Particle Script というスクリプトが注入されます。
あと SLインワールドに The Particle Laboratory という場所があって そこではいろいろな展示物やサンプルスクリプトなどでパーティクルのことを詳しく教えてもらえます。
SLurl: https://maps.secondlife.com/secondlife/Teal/196/47/302
SL 内でオブジェクトを動かしたり形を変えるようなときイージングというアニメーションの定番的な方法を使うともっともらしいです。
物を動かすとき最初はゆっくりしか動かず はずみがつくと速くなって 止まるときはまたゆっくり というような感じです。
(参照: Prototype easing curves)
SL スクリプトにはそういう仕組みが用意されてないようなので作ってみました。
作り方はベジエ曲線という数学的な曲線(wikipedia Bézier curve)の描き方があって その曲線が時間経過と位置や大きさの関係を表します。
よく使われるのは3次ベジエ曲線/Cubic Bezier Curve で 4つの点 P0,P1,P2,P3 のうち P0=(0,0), P3=(1,1) は固定であとの P1と P2 の設定で 0 から 1 までの時間と変化の関係を決めます。
計算式は B = (1-t)3P0+3(1-t)2tP1+3(1-t)t2P2+t3P3
スクリプトで書けば
vector cubicBezier(float t) {
float t1 = 1.0 - t;
return t1*t1*t1*P0 + 3.0*t1*t1*t*P1 + 3.0*t1*t*t*P2 + t*t*t*P3;
}
ところで曲線を描くのに時間要素 t を変化させますが これは結果の曲線(x,y)の x: アニメーションの経過時間とは微妙に違います。
そこで描いた曲線 (x,y) からアニメーションの経過時間 t に等しい x に対応する変化量 y の値を 計算するには 曲線上の前後の点から補間します。
float easing(integer i) {
if (i<=0) return 0.0; // 最初
else if (N<=i) return 1.0; // 最後
float t = (float)i/(float)N; // 経過時間→曲線を描くときにも使う
vector b = cubicBezier(t); // 描いた曲線上の点
if (t<=b.x) --i; // 1つ前
else ++i; // 1つ後ろ
float t0 = (float)i/(float)N;
vector b0 = cubicBezier(t0); // 曲線上の隣の点
float r = (b.x-t)/(b.x-b0.x); // 時間の差と隣までの差の比率
vector br = b - r*(b-b0); // 補間した経過時間の曲線上の点
return br.y;
}
補間計算のための数値は前後1つ隣しか見てませんが 同じ数で時間を刻めば描いた曲線の点も経過時間に近くて 変化が激しくなければ誤差もきっと小さいでしょう。
このアニメーションは lovemax ベネチア店の最上階 お城のデモを見に行く箱に仕込みました。
ほかにももう一つ わざとわかりにくくした場所にも仕掛けてますが 怪しいところを探せばきっと…。
西洋のお城の塔にある螺旋階段は登るとき時計回りが基本だそうです。
その話をディスカバリーチャンネルか何かで見て 売ってるお城(参照: お城と岩のセット販売)を確認したら何も考えずに作ってたのでやっぱり逆回りでした。(><)
作ったときは半分に削ったトーラス(ドーナツ状のプリム)を引き伸ばして螺旋階段に見立ててましたが最近の何でもメッシュの世の中に迎合してついに階段だけメッシュ部品と入れ替えました。
すると部品は 1プリムでランドインパクトが 2 なのに大きさを変えるとランドインパクトが 3 になったりほかのプリムとリンクすると全体のランドインパクトがもっと増えるとか不思議なことが起こります。
もとのお城はプリムだけでできていて19プリム=19ランドインパクトですが 階段をメッシュ部品に入れ替えたら同じプリム数でもランドインパクトが 34になるのです!
…メッシュは謎です…。
ところで螺旋階段は本当に時計回りが多いか画像検索してみたら…逆回りもたくさんあります??
しかもお城の攻防で逆の説も出てきました:
螺旋階段のステップは扇型で柱側が狭く壁側が広いので兵士は足場を確保するため壁側に寄る そのとき攻め登る側は反時計回りの階段のほうが右手が壁に近くて自由が利かずに不利…。
…うーんどっちが正しいのか どっちも単なる都市伝説なのか…。
ちなみに lovemax の製品 MyCastle では いちいち階段を登らなくても柱にタッチ→メニューで行き先を選んでジャンプ で行けるので どっち回りか気にしなくてOK!
MyCastle はベネチアのお店の最上階から箱に乗って上空でデモを見ることができます。
あと 上空に行くと作りかけのベネチアの街も見ることができてベネチア SIM がどうやって作られているか雰囲気がなんとなくわかります。