お使いのブラウザは最新版ではありません。最新のブラウザでご覧ください。

CNET Japan ブログ

Second Life(セカンドライフ) - タイニーアバター検証(2):AO(Animation Override アニメーション・オーバーライド)

2007/10/28 23:30
  • このエントリーをはてなブックマークに追加

ミキ・オキタWebClip ウェブデザインのニュース


アニメーションを繰り返し自動的に再生し続ける

こんにちは。“時代の3歩先をねらうWeb屋さん”ミキ・オキタです。
このブログ「WebClip ウェブデザインのニュース」では、Webデザイン・Webマーケティングの話題をお届けしています。最近はSecond Life (セカンドライフ)が中心です。

前々回より、別のオンラインゲーム「女神転生IMAGINE」に出てくる「エアロス」というキャラクターをアバター化しようと目論んでいる。
前々回の記事では身体の一部を透明にするインビジプリム(透明化プリム)を、前回は身体を折りたたむためのアニメーションの優先度について検証した。
今回は前回を受けて、AO(Animation Override アニメーション・オーバーライド)について解析する。

20071014_aeros_s.jpg
参考までにエアロスの画像を再掲。

AO(Animation Override アニメーション・オーバーライド)

Second Life(セカンドライフ)のアバターは、初期設定でも、立っているだけで何らかのアニメーションを再生している。たとえば、立つ、歩く、飛ぶ・・・など、すべてアニメーションだ。
身体を折りたたみ続けるためには、優先度を最高に設定した身体を折りたたむアニメーションを常に再生していればいい。

ただし、前回の検証から、優先度4でも初期設定のアニメーションに上書きされてしまうケースや、シム(SIM)の境界をまたぐとアニメーションが停止してしまうケースがあることがわかった。

これを回避するためには、AO(アニメーション・オーバーライド)の技術を応用したらどうか?

AO(アニメーション・オーバーライド)とは、アバターに初期設定されたアニメーションを上書きしてくれるオブジェクトだ。
街中を歩いていて、ときどき美しい身のこなしで歩く女性アバターを見かけたことはないだろうか。

Second Life(セカンドライフ)のアバターに初期設定されているアニメーションは、たとえば女性のアバターであったとしても、大股で立ったり、胸をいからせるようにして歩いたりと、けして美しいとは言いがたい。
AO(アニメーション・オーバーライド)は、初期設定されているこのようなアニメーションに対して、自分の好きなアニメーションで上書き再生してくれる機能を持っている。

AO(アニメーション・オーバーライド)がどのようにしてアニメーションを再生し続けているかわかれば、常に身体を折りたたむアニメーションを再生することができる。

フリーのAO「ZHAO」とその基本

今回、解析のために用意したAO(アニメーション・オーバーライド)は「ZHAO(Ziggy's HUD Animation Overrider)」というもの。

「ZHAO」は以下の場所から入手可能だ。
Outrider Animation
http://slurl.com/secondlife/Outrider/129/193/34/ (セカンドライフが起動します)

また、使い方は以下のブログの記事がわかりやすい。

Time after Time - アニメーション置き換え(SS追加版)

Assumption of the Virgin - As You Like It

以下に「ZHAO」のスクリプトを解析していく。

ZHAOの基本構造は「Empty」という名前のノートカードに書かれたアニメーションのファイル名を読み込み、アバターの動作に合わせて上書きすべき該当のアニメーションを再生する、という構造をしている。
「Empty」の項目を見ると、かなり細かく動作が分かれていることがわかる。
たとえば、「飛ぶ」という動作を取っても「Soft Landing(軟着陸)」「Falling Down(落ちる)」「Hovering Down(飛んで下降する)」「Hovering Up(飛んで上昇する)」「FlyingSlow(ゆっくり飛ぶ)」「Flying(飛ぶ)」「Hovering(浮く)」「Landing(着陸する)」と分かれている。
これらのうち、自分が上書きしたい部分にアニメーションのファイル名を書きこむことで、初期設定のアニメーションに上書き再生することができる。とくに設定しなければ、初期設定のものがそのまま再生される。

気をつけなければならないのは「Standing(立つ)」の項目だ。
「Empty」の項目で「Standing(立つ)」だけでも1?5まであるが、もし「立つ」のアニメーションを上書きしたいときは、この1?5すべてに対してアニメーションを設定してあげないといけない。
アバターを初期設定のままぼんやり立たせているとわかるが、アバターの立ちアニメーションは初期設定で数種類あり、それらが順番に再生されている。
「Standing 1」にだけ上書き用のアニメーションを設定しても、残りの2?5のアニメーションに何も設定されていないと、「Standing 1」以外の2?5が再生されているときは初期設定のアニメーションが再生されてしまう。

「ZHAO」のスクリプト解析

それでは「ZHAO(Ziggy's HUD Animation Overrider)」は、どのようにしてアニメーションを上書きしているのだろう?
以下に「ZHAO(Ziggy's HUD Animation Overrider)」のスクリプトに説明を加えて掲載した。
説明の日本語はすべて僕がつけたものだが、例によって翻訳の素人のため、正確性は保証できない。参考程度にしてほしい。

// 1.0.11.2 Fixed forward walk override (same as previous backward walk fix). 09/06 by Dzonatas Sol

// Ziggy's HUD Animation Overrider - 05/06

// Based on Francis Chung's Franimation Overrider v1.8

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

// CONSTANTS コンテンツ
//////////////////
// Default notecard we read on script_entry スクリプト開始時に読み込むノートカードの名前
string defaultNoteCard = "Empty"; // ノートカードの名前は「Empty」

// List of all the animation states すべてのアニメーション状態のリスト
list animState = ["Sitting on Ground", "Sitting", "Striding", "Crouching", "CrouchWalking",
"Soft Landing", "Standing Up", "Falling Down", "Hovering Down", "Hovering Up",
"FlyingSlow", "Flying", "Hovering", "Jumping", "PreJumping", "Running",
"Turning Right", "Turning Left", "Walking", "Landing", "Standing" ];

// Animations in which we automatically disable animation overriding
// Try to keep this list short, the longer it is the worse it affects our runtime
// (Note: This is *almost* constant. We have to type-convert this to keys instead of strings
// on initialization - blargh)
// アニメーションの上書き再生が自動的に不可になるもののアニメーション
// リストを短くするようにしなさい。長いほどランタイムに影響を及ぼす。
// (注意:これはほとんど普遍的にそうなる。初期化での文字列は代わりにキーを用いるべき)
list autoDisableList = [
"3147d815-6338-b932-f011-16b56d9ac18b", // aim_R_handgun ハンドガン
"ea633413-8006-180a-c3ba-96dd1d756720", // aim_R_rifle ライフル
"b5b4a67d-0aee-30d2-72cd-77b333e932ef", // aim_R_bazooka バズーカ
"46bb4359-de38-4ed8-6a22-f1f52fe8f506", // aim_l_bow 弓
"9a728b41-4ba0-4729-4db7-14bc3d3df741", // Launa's hug
"f3300ad9-3462-1d07-2044-0fef80062da0", // punch_L 左パンチ
"c8e42d32-7310-6906-c903-cab5d4a34656", // punch_r 右パンチ
"85428680-6bf9-3e64-b489-6f81087c24bd", // sword_strike_R 剣を振る
"eefc79be-daae-a239-8c04-890f5d23654a" // punch_onetwo ワンツーパンチ
];

// Index of interesting animations 主要なアニメーションのインデックス
integer noAnimIndex = -1;
integer standIndex = 20;
integer sittingIndex = 1;
integer sitgroundIndex = 0;
integer hoverIndex = 12;
integer flyingIndex = 11;
integer flyingslowIndex = 10;
integer hoverupIndex = 9;
integer hoverdownIndex = 8;
integer waterTreadIndex = 25;
integer swimmingIndex = 26;
integer swimupIndex = 27;
integer swimdownIndex = 28;
integer standingupIndex = 6;
integer walkingIndex = 18;

// list of animations that have a different value when underwater
// 水の中にいるとき、アニメーションのリストは異なる値が必要
list underwaterAnim = [ hoverIndex, flyingIndex, flyingslowIndex, hoverupIndex, hoverdownIndex ];

// corresponding list of animations that we override the overrider with when underwater
// 水の中にいるとき、上書きを上書きする一致したアニメーションのリスト
list underwaterOverride = [ waterTreadIndex, swimmingIndex, swimmingIndex, swimupIndex, swimdownIndex];

list lineNums = [ 45, // 0 Sitting on Ground 地面に座る
33, // 1 Sitting プリムに座る
1, // 2 Striding 大股で歩く
17, // 3 Crouching しゃがむ
5, // 4 CrouchWalking しゃがんで歩く
39, // 5 Soft Landing 軟着陸
41, // 6 Standing Up 立ち上がる
37, // 7 Falling Down 落ちる
19, // 8 Hovering Down 飛んで下降する
15, // 9 Hovering Up 飛んで上昇する
43, // 10 FlyingSlow ゆっくり飛ぶ
7, // 11 Flying 飛ぶ
31, // 12 Hovering 浮く
13, // 13 Jumping ジャンプする
35, // 14 PreJumping ジャンプしようとする
3, // 15 Running 走る
11, // 16 Turning Right 右を向く
9, // 17 Turning Left 左を向く
1, // 18 Walking 歩く
39, // 19 Landing 着陸する
21, // 20 Standing 1 立つ 1

// Extra...

23, // 21 Standing 2 立つ 2
25, // 22 Standing 3 立つ 3
27, // 23 Standing 4 立つ 4
29, // 24 Standing 5 立つ 5
47, // 25 Treading Water 水の中を歩く
49, // 26 Swimming 泳ぐ
51, // 27 Swim up 泳いで上昇する
53, // 28 Swim Down 泳いで下降する
55, // 29 Walking 2 歩く 2
57, // 30 Walking 3 歩く 3
59, // 31 Walking 4 歩く 4
61, // 32 Walking 5 歩く 5
63, // 33 Sitting 2 プリムに座る 2
65, // 34 Sitting 3 プリムに座る 3
67, // 35 Sitting 4 プリムに座る 4
69, // 36 Sitting 5 プリムに座る 5
71, // 37 Ground Sitting 2 地面に座る 2
73, // 38 Ground Sitting 3 地面に座る 3
75, // 39 Ground Sitting 4 地面に座る 4
77 // 40 Ground Sitting 5 地面に座る 5
]; // ZHAOでは、チャット用のタイピングアニメーションは別の箇所で設定されている

// This is an ugly hack, because the standing up animation doesn't work quite right
// (SL is borked, this has been bug reported)
// If you play a pose overtop the standing up animation, your avatar tends to get
// stuck in place.
// これは愚かなハック。なぜなら「立ち上がる」アニメーションは非常に正しく動かない。
// もし「立ち上がる」アニメーションを再生したければ、アバターを場所でstuckさせる。
// This is a list of anims that we'll stop automatically
// これは自動的に停止するアニメーションのリスト
list autoStop = [ 5, 6, 19 ];
// Amount of time we'll wait before autostopping the animation (set to 0 to turn off autostopping )
// アニメーションの自動停止前に待つ時間(0にセットすめと自動停止を無効にする)
float autoStopTime = 1.5;

// List of stands「立つ」アニメーションの番号リスト
list standIndexes = [ 20, 21, 22, 23, 24 ];

// How long before flipping stand animations「立つ」アニメーションを切り変えるまでの時間
float standTimeDefault = 40.0;

// List of sits 「プリムに座る」アニメーションの番号リスト
list sitIndexes = [ 1, 33, 34, 35, 36 ];

// List of walks 「歩く」アニメーションの番号リスト
list walkIndexes = [ 18, 29, 30, 31, 32 ];

// List of ground sits 「地面に座る」アニメーションの番号リスト
list gsitIndexes = [ 0, 37, 38, 39, 40 ];

// How fast we should poll for changed anims (as fast as possible)
// In practice, you will not poll more than 8 times a second.
// アニメーションの切り替えにどのくらいの速さで行うか(可能な限り速く)。
// 実際のところ、1秒あたり8回以上はできない。
float timerEventLength = 0.1;

// The minimum time between events.
// While timerEvents are scaled automatically by the server, control events are processed
// much more aggressively, and needs to be throttled by this script
// イベントとイベントのあいだの最低時間。
// タイマーは自動的にサーバによって計られている間、イベントは積極的に処理され、このスクリプトによって圧迫されるようになる。
float minEventDelay = 0.1;

// The key for the typing animation
// タイピングアニメーションのキー
key typingAnim = "c541c47f-e0c0-058b-ad1a-d6ae3a4584d9";

// List of states where llGetAnimation() might give us something goofy back
// llGetAnimation()が愚かな反応をしたときの状態のリスト
list hackGetAnimList = ["CrouchWalking"];

// Listen channel for pop-up menu
// ポップアップメニューの声を聞くチャンネル
integer listenChannel = -91234;

// GLOBALS クローバル変数
//////////////////

list stands = [ "", "", "", "", "" ]; // List of stand animations 「立つ」アニメーションのリスト
integer curStandIndex = 0; // Current stand we're on (indexed [0, numStands])
string curStandAnim = ""; // Current Stand animation
integer numStands; // # of stand anims we use (constant: ListLength(stands))
integer curStandAnimIndex = 0; // Current stand we're on (indexed [0, numOverrides] )

list sits = [ "", "", "", "", "" ]; // List of sit animations 「座る」アニメーションのリスト
integer curSitIndex = 0; // Current sit we're on (indexed [0, numSits])
string curSitAnim = ""; // Current sit animation
integer numSits; // # of sit anims we use (constant: ListLength(sits))
integer curSitAnimIndex = 0; // Current sit we're on (indexed [0, numOverrides] )

list walks = [ "", "", "", "", "" ]; // List of walk animations 「歩く」アニメーションのリスト
integer curWalkIndex = 0; // Current walk we're on (indexed [0, numWalks])
string curWalkAnim = ""; // Current walk animation
integer numWalks; // # of walk anims we use (constant: ListLength(walks))
integer curWalkAnimIndex = 0; // Current walk we're on (indexed [0, numOverrides] )

list gsits = [ "", "", "", "", "" ]; // List of ground sit animations 「地面に座る」アニメーションのリスト
integer curGsitIndex = 0; // Current ground sit we're on (indexed [0, numGsits])
string curGsitAnim = ""; // Current ground sit animation
integer numGsits; // # of ground sit anims we use (constant: ListLength(gsits))
integer curGsitAnimIndex = 0; // Current ground sit we're on (indexed [0, numOverrides] )

list overrides = []; // List of animations we override 上書きするアニメーションのリスト
key notecardLineKey; // notecard reading keys
integer notecardLinesRead; // number of notecard lines read
integer notecardIndex; // current line beingr ead from notecard
integer numOverrides; // # of overrides (a constant - llGetListLength(lineNums))

string lastAnim = ""; // last Animation we ever played 最後に再生したアニメーション
string lastAnimSet = ""; // last set of animations we ever played 最後に再生したアニメーションセット
integer lastAnimIndex = 0; // index of the last animation we ever played 最後に再生したアニメーションのインデックス
string lastAnimState = ""; // last thing llGetAnimation() returned llGetAnimation()が返した最後のもの
float standTime = standTimeDefault; // How long before flipping stand animations 立つアニメーションの切り替え時間にstandTimeDefaultを代入

integer animOverrideOn = TRUE; // Is the animation override on? アニメーション上書きをするかどうかのフラグ
integer gotPermission = FALSE; // Do we have animation permissions? パーミッションが取れているかのフラグ

integer listenHandle; // Listen handlers - only used for pop-up menu, then turned off ポップアップメニューのためだけに使う

integer haveWalkingAnim = FALSE; // Hack to get it so we face the right way when we walk backwards 後ろに歩いたときに正しく動作させるためのハック

integer sitOverride = TRUE; // Whether we're overriding sit or not 「プリムに座る」ときのアニメーション上書きをするかどうかのフラグ

integer listenState = 0; // What pop-up menu we're handling now どのポップアップメニューをつかんでいるか

integer loadInProgress = FALSE; // Are we currently loading a notecard ノートカードのロード中かどうかのフラグ
string notecardName = ""; // The notecard we're currently reading 今読み込んでいるノートカード

// CODE コード
//////////////////

// Find if two lists/sets share any elements in common
integer hasIntersection( list _list1, list _list2 ) {
list bigList;
list smallList;
integer smallListLength;
integer i;

if ( llGetListLength( _list1 ) <= llGetListLength( _list2 ) ) {
smallList = _list1;
bigList = _list2;
}
else {
bigList = _list1;
smallList = _list2;
}
smallListLength = llGetListLength( smallList );

for ( i=0; i if ( llListFindList( bigList, llList2List(smallList,i,i) ) != -1 ) {
return TRUE;
}
}

return FALSE;
}

startAnimationList( string _csvAnims ) {
list anims = llCSV2List( _csvAnims ); // _csvAnimsからcsvをリスト要素animsへ
integer numAnims = llGetListLength( anims ); // animsのリスト件数を取得
integer i;
for( i=0; i llStartAnimation( llList2String(anims,i) ); // animsの該当アニメーション名を再生する
}

stopAnimationList( string _csvAnims ) {
list anims = llCSV2List( _csvAnims ); // _csvAnimsからcsvをリスト要素animsへ
integer numAnims = llGetListLength( anims ); // animsのリスト件数を取得
integer i;
for( i=0; i llStopAnimation( llList2String(anims,i) ); // animsの該当アニメーション名を停止する
}

startNewAnimation( string _anim, integer _animIndex, string _state ) {
if ( _anim != lastAnimSet ) { // 受け取った第1変数_animがlastAnimSetと同じでなければ
string newAnim; // 文字型変数newAnimを生成
if ( lastAnim != "" ) // lastAnimが空でなければ
stopAnimationList( lastAnim ); // lastAnimを引数にアニメーションを停める
if ( _anim != "" ) { // Time to play a new animation 新しいアニメーションを再生する時間、受け取った第1変数_animが空でなければ
list newAnimSet = llParseStringKeepNulls( _anim, ["|"], [] ); 「|」をデメリタにして受け取った第1変数_animをリストnewAnimSet化
newAnim = llList2String( newAnimSet, (integer)llFloor(llFrand(llGetListLength(newAnimSet))) ); // 0からnewAnimSetのリスト件数までの間で乱数を発生させ、小数点以下の端数を切り捨てた整数番目の要素を、リストnewAnimSetから取得してnewAnimに入れる

startAnimationList( newAnim ); // リストnewAnimを引数に渡してアニメーションを再生する

if ( llListFindList( autoStop, [_animIndex] ) != -1 ) { // リストautoStopのなかに受け取った第2変数_animIndexがなければ
// This is an ugly hack, because the standing up animation doesn't work quite right
// (SL is borked, this has been bug reported)
// If you play a pose overtop the standing up animation, your avatar tends to get
// stuck in place.
// これは愚かなハック。なぜなら「立ち上がる」アニメーションは非常に正しく動かない。
// もし「立ち上がる」アニメーションを再生したければ、アバターを場所でstuckさせる。
if ( lastAnim != "" ) { // lastAnimが空でなければ
stopAnimationList( lastAnim ); // lastAnimを引数にアニメーションを停止
lastAnim = ""; // lastAnimを空にする
}
llSleep( autoStopTime ); // autoStopTime時間だけスクリプトを停止
stopAnimationList( _anim ); // 受け取った第1変数_animを引数にアニメーションを停める
}
}
lastAnim = newAnim; // lastAnimに今回再生したアニメーションの名前newAnimを入れる
lastAnimSet = _anim; // lastAnimSetに今回受け取った第1変数_animを入れる
}
lastAnimIndex = _animIndex; // lastAnimIndexに今回受け取った第2変数_animIndexを入れる
lastAnimState = _state; // lastAnimStateに今回受け取った第3変数_stateを入れる
}

// Load all the animation names from a notecard ノートカードからアニメーションの名前をロードする
loadNoteCard() {

if ( llGetInventoryKey(notecardName) == NULL_KEY ) {
llOwnerSay( "Notecard '" + notecardName + "' does not exist, or does not have full permissions." );
loadInProgress = FALSE;
notecardName = "";
return;
}

llOwnerSay( "Loading notecard '" + notecardName + "'..." );

// Faster events while processing our notecard
llMinEventDelay( 0 );

// Start reading the data
notecardLinesRead = 0;
notecardIndex = 0;
notecardLineKey = llGetNotecardLine( notecardName, llList2Integer(lineNums, notecardIndex) );
}

// Figure out what animation we should be playing right now 何のアニメーションが再生されるべきか理解する
animOverride() {
string curAnimState = llGetAnimation(llGetOwner());
integer curAnimIndex;
integer underwaterAnimIndex;
vector curPos;

// Check if we need to work around any bugs in llGetAnimation llGetAnimationのバグに直面する必要があるならチェック
if ( llListFindList(hackGetAnimList, [curAnimState]) != -1 ) {

// Hack, because, SL really likes to switch between crouch and crouchwalking for no reason ハック。なぜなら、SLは本当に何の理由もなく「crouch」「crouchwalking」のあいだを切り替えようとする
if ( curAnimState == "CrouchWalking" ) { // もしcurAnimStateが「CrouchWalking」なら
if ( llVecMag(llGetVel()) < .5 )
curAnimState = "Crouching"; // curAnimStateを「crouch」にする
}
}

if ( curAnimState == lastAnimState ) {
// This conditional not absolutely necessary (In fact it's better if it's not here)
// But it's good for increasing performance.
// One of the drawbacks of this performance hack is the underwater animations
// If you fly up, it will keep playing the "swim up" animation even after you've
// left the water.
// この条件節は絶対的に必要なものではない (実際のところ、ここにないならないほうがよい)
// ただし、パフォーマンス増加に良い。
// パフォーマンスのハックの欠点のひとつは水の中のアニメーションだ。
// もしあなたが飛んで上昇しても、あなたが水を離れても、「泳いで上昇する」アニメーションを維持し続けるだろう
return;
}

curAnimIndex = llListFindList( animState, [curAnimState] );
underwaterAnimIndex = llListFindList( underwaterAnim, [curAnimIndex] );
curPos = llGetPos(); // 親Primのグローバル座標

if ( curAnimIndex == standIndex ) { // curAnimIndexがstandIndexと同じなら
startNewAnimation( curStandAnim, curStandAnimIndex, curAnimState ); // 新しいアニメーションを再生する
}
else if ( curAnimIndex == sittingIndex ) { // curAnimIndexがsittingIndexと同じなら
// Check if sit override is turned off 上書きがオフになっているかチェック
if (( sitOverride == FALSE ) && ( curAnimState == "Sitting" )) { //上書きがオフでcurAnimStateがSittingなら
startNewAnimation( "", noAnimIndex, curAnimState ); // 新しいアニメーションを再生する(再生をやめる)
}
else { // 上書きがオンなら
startNewAnimation( curSitAnim, curSitAnimIndex, curAnimState ); // 新しいアニメーションを再生する
}
}
else if ( curAnimIndex == walkingIndex ) { // curAnimIndexが walkingIndexと同じなら
startNewAnimation( curWalkAnim, curWalkAnimIndex, curAnimState ); // 新しいアニメーションを再生する
}
else if ( curAnimIndex == sitgroundIndex ) { // curAnimIndexがsitgroundIndexと同じなら
startNewAnimation( curGsitAnim, curGsitAnimIndex, curAnimState ); // 新しいアニメーションを再生する
}
else {
if ( underwaterAnimIndex != -1 && llWater(ZERO_VECTOR) > curPos.z ) // 泳ぐアニメーションを再生するかどうか
curAnimIndex = llList2Integer( underwaterOverride, underwaterAnimIndex );
startNewAnimation( llList2String( overrides, curAnimIndex ), curAnimIndex, curAnimState ); // 新しいアニメーションを再生する
}
}

// Switch to the next stand anim 次の「立つ」アニメーションに切り変える
doNextStand() {
curStandIndex = (curStandIndex+1) % numStands;
curStandAnimIndex = llList2Integer(standIndexes,curStandIndex);
curStandAnim = llList2String(overrides, curStandAnimIndex);
if ( lastAnimState == "Standing" )
startNewAnimation( curStandAnim, curStandAnimIndex, lastAnimState );
llResetTime();
}

// Returns true if we should override the current animation 今のアニメーションを上書きすべきならtrueを返す
integer shouldOverride() {
if ( animOverrideOn && gotPermission ) {
// Check if we should explicitly NOT override a playing animation
if ( hasIntersection( autoDisableList, llGetAnimationList(llGetOwner()) ) ) {
startNewAnimation( "", noAnimIndex, "" );
return FALSE;
}
return TRUE;
}
return FALSE;
}

// Initialize listeners, and reset some status variables リスナーを初期化し、ステータス変数をリセットする
initialize() {
if ( animOverrideOn ) // 上書きがオンなら
llSetTimerEvent( timerEventLength ); // タイマーをセット
else // 上書きがでなければ
llSetTimerEvent( 0 ); // タイマーを終了

lastAnim = ""; // lastAnimを空にする
lastAnimIndex = noAnimIndex; // lastAnimIndexにnoAnimIndexを入れる
lastAnimState = ""; // lastAnimStateを空にする
gotPermission = FALSE; // gotPermissionは取れていない

if ( listenHandle ) // listenが走っていたら
llListenRemove( listenHandle ); // listenを解除

llOwnerSay( (string) llGetFreeMemory() + " bytes free" ); // 所有者にメモリ残量を言う
}

// STATE ステート(状態)
//////////////////

default {
state_entry() { //最初に
integer i;

if ( llGetAttached() ) // アタッチメントを身につけていれば
llRequestPermissions(llGetOwner(),PERMISSION_TRIGGER_ANIMATION|PERMISSION_TAKE_CONTROLS); // 所有者にアニメーションと操作のパーミッションの許可を求める

// Initialize! 初期化
numStands = llGetListLength( stands ); // 「立つ」アニメーションのリストの件数を取得
curStandAnimIndex = llList2Integer(standIndexes,curStandIndex);

numSits = llGetListLength( sits ); // 「プリムに座る」アニメーションのリストの件数を取得
curSitAnimIndex = llList2Integer(sitIndexes,curSitIndex);

numWalks = llGetListLength( walks ); // 「歩く」アニメーションのリストの件数を取得
curWalkAnimIndex = llList2Integer(walkIndexes,curWalkIndex);

numOverrides = llGetListLength(lineNums);

// Type convert strings to keys :P
for ( i=0; i key k = llList2Key( autoDisableList, i );
autoDisableList = llListReplaceList ( autoDisableList, [ k ], i, i );
}

// populate override list with blanks 上書きのリストに空白を含める
for ( i=0; i overrides += [ "" ];
}
initialize(); // 初期化
notecardName = defaultNoteCard; // ノートカードの名前
loadInProgress = TRUE; // 読み込み中のフラグ
loadNoteCard(); // ノートカードの読み込み

// turn off the auto-stop anim hack アニメーションの自動停止ハックを無効
if ( autoStopTime == 0 ) // もしautoStopTimeが0なら
autoStop = [];

llResetTime(); // タイマーの稼働時間を0に戻す
}

run_time_permissions(integer _parm) { // パーミッションへの反応があった
if( _parm != (PERMISSION_TRIGGER_ANIMATION|PERMISSION_TAKE_CONTROLS) )
gotPermission = FALSE; // パーミッションが取得できていなかった
else {
llTakeControls( CONTROL_BACK|CONTROL_FWD, TRUE, TRUE);
gotPermission = TRUE; // パーミッションが取得できていた
}
}

attach( key _k ) { // 身につけたら
if ( _k != NULL_KEY )
llRequestPermissions(llGetOwner(),PERMISSION_TRIGGER_ANIMATION|PERMISSION_TAKE_CONTROLS); // 所有者にアニメーションと操作のパーミッションの許可を求める
}

link_message( integer _sender, integer _channel, string _message, key _id ) { // プリムから送られたメッセージを受信すると
// 各主要なアニメーションについて、上書きの開始/停止
if ( _message == "On" ) {
llSetTimerEvent( timerEventLength ); // timerEventLengthごとにtimerステートを呼び出す
animOverrideOn = TRUE; // animOverrideOnのフラグを立てる
if ( gotPermission ) // パーミッションが取れていれば
animOverride(); // アニメーションを上書きする
}
else if ( _message == "Off" ) {
llSetTimerEvent( 0 ); // タイマーを終了する
animOverrideOn = FALSE; // animOverrideOnのフラグを消す
startNewAnimation( "", noAnimIndex, lastAnimState ); // 新しいアニメーションを開始する
}
else if ( _message == "Load" ) { // Loadボタンが押されたら

// Can't load while we're in the middle of a load ロード中に更にロードできない
if ( loadInProgress == TRUE ) {
llOwnerSay( "Cannot load new notecard, still reading notecard \"" + notecardName + "\"" );
return;
}

integer n = llGetInventoryNumber( INVENTORY_NOTECARD );
// Can only have 12 buttons in a dialog box
if ( n > 12 ) {
llOwnerSay( "You cannot have more than 12 animation notecards." );
return;
}

integer i;
list animSets = [];

// Build a list of notecard names and present them in a dialog box
for ( i = 0; i < n; i++ ) {
animSets += [ llGetInventoryName( INVENTORY_NOTECARD, i ) ];
}

if ( listenHandle )
llListenRemove( listenHandle );

listenHandle = llListen( listenChannel, "", llGetOwner(), "" );
llDialog( llGetOwner(), "Select the notecard to load:", animSets, listenChannel );
listenState = 1;
}
else if ( _message == "Sit On" ) { // 「座る」開始ボタンが押されたら
// Turning on sit override
sitOverride = TRUE;
if ( lastAnimState == "Sitting" )
startNewAnimation( curSitAnim, curSitAnimIndex, lastAnimState );
}
else if ( _message == "Sit Off" ) { // 「座る」停止ボタンが押されたら
// Turning off sit override
sitOverride = FALSE;
if ( lastAnimState == "Sitting" )
startNewAnimation( "", noAnimIndex, lastAnimState );
}
else if ( _message == "Sit" ) { // 「座る」ボタンが押されたら
// Selecting new sit anim

if ( listenHandle )
llListenRemove( listenHandle );

listenHandle = llListen( listenChannel, "", llGetOwner(), "" );
llDialog( llGetOwner(), "Select the sit animation to use:", [ "1", "2", "3", "4", "5" ], listenChannel );
listenState = 2;
}
else if ( _message == "Walk" ) { // 「歩く」ボタンが押されたら
// Same thing for the walk

if ( listenHandle )
llListenRemove( listenHandle );

listenHandle = llListen( listenChannel, "", llGetOwner(), "" );
llDialog( llGetOwner(), "Select the walk animation to use:", [ "1", "2", "3", "4", "5" ], listenChannel );
listenState = 3;
}
else if ( _message == "GroundSit" ) { // 「地面に座る」ボタンが押されたら
// And the ground sit

if ( listenHandle )
llListenRemove( listenHandle );

listenHandle = llListen( listenChannel, "", llGetOwner(), "" );
llDialog( llGetOwner(), "Select the ground sit animation to use:", [ "1", "2", "3", "4", "5" ], listenChannel );
listenState = 4;
}
}

listen( integer _channel, string _name, key _id, string _message) {

llListenRemove( listenHandle );
listenHandle = 0;

if ( listenState == 1 ) {
// Notecard menu ノートカード
loadInProgress = TRUE;
notecardName = _message;
loadNoteCard(); // ノートカードを読み込む
}
else if ( listenState == 2 ) {
// Sit menu 座るメニュー
curSitIndex = (integer)_message - 1;
curSitAnimIndex = llList2Integer( sitIndexes, curSitIndex );
curSitAnim = llList2String( overrides, curSitAnimIndex );

if ( lastAnimState == "Sitting" ) // 最後のアニメーションが「座る」だったら
startNewAnimation( curSitAnim, curSitAnimIndex, lastAnimState ); // 新しいアニメーションを開始

llOwnerSay( "New sitting animation: " + curSitAnim );; // オーナーに向かって言う
}
else if ( listenState == 3 ) {
// Walk menu 歩くメニュー
curWalkIndex = (integer)_message - 1;
curWalkAnimIndex = llList2Integer( walkIndexes, curWalkIndex );
curWalkAnim = llList2String( overrides, curWalkAnimIndex );

if ( lastAnimState == "Walking" ) // 最後のアニメーションが「歩く」だったら
startNewAnimation( curWalkAnim, curWalkAnimIndex, lastAnimState ); // 新しいアニメーションを開始

llOwnerSay( "New walking animation: " + curWalkAnim ); // オーナーに向かって言う
}
else if ( listenState == 4 ) {
// Ground sit menu
curGsitIndex = (integer)_message - 1;
curGsitAnimIndex = llList2Integer( gsitIndexes, curGsitIndex );
curGsitAnim = llList2String( overrides, curGsitAnimIndex );

if ( lastAnimState == "Sitting on Ground" ) // 最後のアニメーションが「地面に座る」だったら
startNewAnimation( curGsitAnim, curGsitAnimIndex, lastAnimState ); // 新しいアニメーションを開始

llOwnerSay( "New sitting on ground animation: " + curGsitAnim ); // オーナーに向かって言う
}

listenState = 0;
}

dataserver( key _query_id, string _data ) { // dataserverステート(ノートカードの読み込み)

if ( _query_id != notecardLineKey ) { // ノートカードの読み込みエラー
llOwnerSay( "Error in reading notecard. Please try again." );
loadInProgress = FALSE;
notecardName = "";
return;
}

if ( _data != EOF ) { // not random crap
// ノートカードの読み込み
if ( notecardIndex == curStandAnimIndex ) // Pull in the current stand animation
curStandAnim = _data;

if ( notecardIndex == curSitAnimIndex ) // Pull in the current sit animation
curSitAnim = _data;

if ( notecardIndex == curWalkAnimIndex ) // Pull in the current walk animation
curWalkAnim = _data;

if ( notecardIndex == curGsitAnimIndex ) // Pull in the current ground sit animation
curGsitAnim = _data;

// Whoops, we're replacing the currently playing anim
if ( animOverrideOn && gotPermission && notecardIndex == lastAnimIndex ) {

// Better play the new one :)
startNewAnimation( _data, lastAnimIndex, lastAnimState );
}

// Do we have a walking animation?
if ( notecardIndex == walkingIndex )
haveWalkingAnim = !( _data == "" );

// Store the name of the new animation
overrides = llListReplaceList( overrides, [_data], notecardIndex, notecardIndex );

notecardLinesRead ++;

// See if we're done loading the notecard. Users like status messages.
if ( notecardLinesRead < numOverrides ) {
notecardIndex ++;
notecardLineKey = llGetNotecardLine( notecardName, llList2Integer(lineNums, notecardIndex) );
}
else {
llOwnerSay( "Finished reading notecard " + notecardName + ". (" +
(string) llGetFreeMemory() + " bytes free)" );
loadInProgress = FALSE;
notecardName = "";

// Restore the minimum event delay
llMinEventDelay( minEventDelay );
}
}
else {
// We hit EOF, so it's a short notecard - probably an original ノートカードの読み込み完了

llOwnerSay( "Finished reading notecard " + notecardName + ". (" +
(string) llGetFreeMemory() + " bytes free)" );
loadInProgress = FALSE;
notecardName = "";

// Restore the minimum event delay
llMinEventDelay( minEventDelay );
}
}

on_rez( integer _code ) { // 地面に置かれたとき
initialize(); // 初期化する
}

collision_start( integer _num ) { // オブジェクトに衝突したとき
if ( shouldOverride() )
animOverride(); // アニメーションを上書きする
}

collision( integer _num ) { // オブジェクトに衝突しているとき
if ( shouldOverride() )
animOverride(); // アニメーションを上書きする
}

collision_end( integer _num ) { // オブジェクトへの衝突から離れたとき
if ( shouldOverride() )
animOverride(); // アニメーションを上書きする
}

control( key _id, integer _level, integer _edge ) { // 操作を取得した
if ( _edge ) {
// SL tends to mix animations together on forward or backward walk. It could be because
// of anim priorities. This helps stop the default walking anims, so it won't mix with
// the desired anim. This also lets the avi turn around on a backwards walk for a more natural
// look.
// SLは前に歩くときと後ろに歩くとき、アニメーションを混ぜる。それはアニメーションの優先度のせいである。
// これは初期設定の歩くアニメーションを停めるのを助ける。つまり求めたアニメーションとは混ざらないということだ。
// これはまた後ろに歩きながら回転するのを自然にする。
if ( _level & _edge & ( CONTROL_BACK | CONTROL_FWD ) ) { // _levelと_edgeと前に進む/後ろに進むが真なら
if ( llGetAnimation(llGetPermissionsKey()) == "Walking" ) {
if ( haveWalkingAnim ) {
llStopAnimation("walk"); // walkアニメーションを停める
llStopAnimation("female_walk"); // 女性のfemale_warkアニメーションを停める
}
}
}

if ( shouldOverride() )
animOverride(); // アニメーションを上書きする
}
}

timer() { // timerステート
if ( shouldOverride() ) {
animOverride(); // アニメーションを上書きする

// Is it time to switch stand animations? 立つアニメーションを次の立つアニメーションにする時間
if ( llGetTime() > standTime ) {
// Don't interrupt the typing animation with a stand change 立つアニメーションを変えるときにタイピングのアニメーションを中断しない
if ( llListFindList(llGetAnimationList(llGetOwner()), [typingAnim]) == -1 )
doNextStand(); // 次の立つアニメーションを再生
}
}
}
}

一定時間ごとにアバターのアニメーションを監視して、最適なアニメーションを再生

結論を言うと「ZHAO」では、一定時間ごとにアバターのアニメーションを監視して、そのときどきに最適なアニメーションを再生している。

また、コメントを読むと、いくつか重要なハックが挿入されている。

  • 「crouch」「crouchwalking」が切り替わる
  • 「立ち上がる」アニメーションがうまく動かない
  • 衝突したときには、即アニメーションを上書きしている

ただし「ZHAO」のスクリプトの冒頭に挿入されたコメントを読むと、2006年の中ごろから年末にかけて作成されたらしく。当時存在したバグが、2007年10月の現時点では既に解消されている可能性も充分にある。

タイニーアバター(Tiny Avatar)制作キット

さて、スクリプトを利用することで、どうやら常に身体を折りたたんだアニメーションを再生し続けることができそうだ。

残された課題としては「折りたたんだ身体がはみでてしまわないように、手足や身体そのもののサイズを小さくすること」、そして「初期設定を上書きするために、最低限用意すべきアニメーションの種類」がわかれば、エアロスアバターがつくれそうだ。

次回は、この課題を解決するために「タイニーアバター(Tiny Avatar)制作キット」を解析する。

[女神転生IMAGINEについて]

© ATLUS
© CAVE
当ブログに掲載されている「女神転生IMAGINE」の画像及び文章等の著作権は、株式会社ケイブ及び株式会社アトラスに帰属します。
無許可転載・転用を禁止します。

女神転生IMAGINE





アクセスカウンタ:

※このエントリは CNET Japan ブロガーにより投稿されたものです。朝日インタラクティブ および CNET Japan 編集部の見解・意向を示すものではありません。
運営事務局に問題を報告

最新ブログエントリー

個人情報保護方針
利用規約
訂正
広告について
運営会社