2008年3月31日 星期一

啥SL—【如何讓字元顯示在Texture上做留言板】


  相信有研究過LSL一段時間的人都會知道,想在Second Life搞出動態的2D圖片,能用到的大概只有llSetTexture之類修改表面材質的method。而想要輸出動態更改的文字,所能用的大概也只有llSetText。因此,我這圖上的Weco Borad Beta,到底是如何做到的呢?

  如果仔細將我上面所提到的東西思考,我想大家應該都想得到原理,那就是準備所有字元的Texture,然後當使用者想要什麼字時,就顯示該Texture。

  可是問題是這要實在是太花錢了,所以在Sloodle(Second Life+Moodle)所提供免費的物件當中,你會發現,他們用了一個非常棒的方法。事實上,每一個被上傳的Texture都會有自己的id值,你只要知道其id值,你就可以利用llSetTexture拿來引用到你的物件上(eg.llSetTexture("701917a8-d614-471f-13dd-5f4644e36e3c", 0);//這個就是將index為0那一面材質設為透明,如何得知該面的index值,可以參考這個影片)。

  而Sloodel很聰明地將ASCII可顯現的95個字元上五個他們自訂的字元用成一個10*10的Texture,如圖:


  然後利用llSetPrimitiveParams一一取出每個字元,看到這裡會不會覺得發明這個的人很屌?他厲害的可還不只這裡,他並不是每一個字元一個Box,他是每五個字元一個Prism,利用參數修改,將五個面呈現在正面,並利用LSL將每個顯示的字元平均顯示在五個面。如圖:

  他的參數是將Prism的Patch Cut Begin and End 的B設為0.199,E設為0.800,Hollow設為68,如此一來,正面就會有五個面,他就利用了一個物件顯示五個字元,大大節省了物件成本(畢竟一個土地的物件量有限)。


  而由於中間的Texture所站的大小和其他Texture顯示不一樣,他還特地利用了llSetPrimitiveParams寫死,取出適當的大小。
  
  簡單來講原理就是這個樣子,而他所寫的程式碼如下:


////////////////////////////////////////////
// XyText v1.2 Script (5 Face, Single Texture)
//
// Written by Xylor Baysklef
//
// Modified by Kermitt Quirk 19/01/2006
// To add support for 5 face prim instead of 3
//
////////////////////////////////////////////

/////////////// CONSTANTS ///////////////////
// XyText Message Map.
integer DISPLAY_STRING = 204000;
integer DISPLAY_EXTENDED = 204001;
integer REMAP_INDICES = 204002;
integer RESET_INDICES = 204003;
integer SET_CELL_INFO = 204004;
integer SET_FONT_TEXTURE = 204005;
integer SET_THICKNESS = 204006;
integer SET_COLOR = 204007;

// This is an extended character escape sequence.
string ESCAPE_SEQUENCE = "\\e";

// This is used to get an index for the extended character.
string EXTENDED_INDEX = "12345";

// Face numbers.
integer FACE_1 = 3;
integer FACE_2 = 7;
integer FACE_3 = 4;
integer FACE_4 = 6;
integer FACE_5 = 1;

// Used to hide the text after a fade-out.
key TRANSPARENT = "701917a8-d614-471f-13dd-5f4644e36e3c";
///////////// END CONSTANTS ////////////////

///////////// GLOBAL VARIABLES ///////////////
// This is the key of the font we are displaying.
key gFontTexture = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
// All displayable characters. Default to ASCII order.
string gCharIndex;
// This is the channel to listen on while acting
// as a cell in a larger display.
integer gCellChannel = 1;
// This is the starting character position in the cell channel message
// to render.
integer gCellCharPosition = 0;
// This is whether or not to use the fade in/out special effect.
integer gCellUseFading = FALSE;
// This is how long to display the text before fading out (if using
// fading special effect).
// Note: < 0 means don't fade out.
float gCellHoldDelay = 1.0;
/////////// END GLOBAL VARIABLES ////////////

ResetCharIndex() {
gCharIndex = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
// \" <-- Fixes LSL syntax highlighting bug.
gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
gCharIndex += "\n\n\n\n\n";
}

vector GetGridOffset(integer index) {
// Calculate the offset needed to display this character.
integer Row = index / 10;
integer Col = index % 10;

// Return the offset in the texture.
return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
}

ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
// Set the primitive textures directly.

// <-0.256, 0, 0>
// <0, 0, 0>
// <0.130, 0, 0>
// <0, 0, 0>
// <-0.74, 0, 0>

llSetPrimitiveParams( [
PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
]);
}

RenderString(string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );

// Use these grid positions to display the correct textures/offsets.
ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}

RenderWithEffects(string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );

// First set the alpha to the lowest possible.
llSetAlpha(0.05, ALL_SIDES);

// Use these grid positions to display the correct textures/offsets.
ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); // Now turn up the alpha until it is at full strength.
float Alpha;
for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
llSetAlpha(Alpha, ALL_SIDES);
// See if we want to fade out as well.
if (gCellHoldDelay < 0.0)
// No, bail out. (Just keep showing the string at full strength).
return;
// Hold the text for a while.
llSleep(gCellHoldDelay);
// Now fade out.
for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
llSetAlpha(Alpha, ALL_SIDES);
// Make the text transparent to fully hide it.
llSetTexture(TRANSPARENT, ALL_SIDES);
}

RenderExtended(string str) {
// Look for escape sequences.
list Parsed = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
integer ParsedLen = llGetListLength(Parsed);

// Create a list of index values to work with.
list Indices;
// We start with room for 5 indices.
integer IndicesLeft = 5;

integer i;
string Token;
integer Clipped;
integer LastWasEscapeSequence = FALSE;
// Work from left to right.
for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
Token = llList2String(Parsed, i);

// If this is an escape sequence, just set the flag and move on.
if (Token == ESCAPE_SEQUENCE) {
LastWasEscapeSequence = TRUE;
}
else { // Token != ESCAPE_SEQUENCE
// Otherwise this is a normal token. Check its length.
Clipped = FALSE;
integer TokenLength = llStringLength(Token);
// Clip if necessary.
if (TokenLength > IndicesLeft) {
Token = llGetSubString(Token, 0, IndicesLeft - 1);
TokenLength = llStringLength(Token);
IndicesLeft = 0;
Clipped = TRUE;
}
else
IndicesLeft -= TokenLength;

// Was the previous token an escape sequence?
if (LastWasEscapeSequence) {
// Yes, the first character is an escape character, the rest are normal.

// This is the extended character.
Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];

// These are the normal characters.
integer j;
for (j = 1; j < TokenLength; j++)
Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
}
else { // Normal string.
// Just add the characters normally.
integer j;
for (j = 0; j < TokenLength; j++)
Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
}

// Unset this flag, since this was not an escape sequence.
LastWasEscapeSequence = FALSE;
}
}

// Use the indices to create grid positions.
vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );

// Use these grid positions to display the correct textures/offsets.
ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}

integer ConvertIndex(integer index) {
// This converts from an ASCII based index to our indexing scheme.
if (index >= 32) // ' ' or higher
index -= 32;
else { // index < 32
// Quick bounds check.
if (index > 15)
index = 15;

index += 94; // extended characters
}

return index;
}


  上面這個程式碼只是他在全域函式的程式碼,並不是完整的LSL code,而你只要把物件調好,在default前放入這個Script,就可以直接利用他寫的RenderString(string str)將五個字元顯示在一個物件之上。

  那本人那個留言板,就是將每個物件編上名字(Mx-y),代表他是顯示留言內容(以M代表)的第x行第y列,而NameX-Y就是顯示名字的第X行第Y列。將好幾個物件組成並改寫他們的名字,之後Link起來,利用llMessageLinked傳送,而這時每個物件就會因為他們的位置不同而取得適當的文字,並將他顯示出來。

  原理大概就是這個樣子,我那個留言板的功能只有當某人點Touch,說話後,他就會顯示是誰在那個板子說了什麼話。我列一下程式碼:
母物件程式碼(只是單純的一個白色板子):

integer listenHandle;

string message1="Please leave one message.There is 200 charactors that you can leave.(only English)";

integer oneLineLimit=20;// a line just has 20 charactors
integer messageLimit=200;//all message just has 200 charactors.

string guestName;
integer LM_NAME=100;
integer LM_MESSAGE=101;

default
{
state_entry()
{
llSetText("WECO Board Beta",<0,1,1>,1);

}

touch_start(integer total)
{
guestName=llDetectedName(0);

listenHandle=llListen(0,guestName,"","");

llSay(0,message1);//tell user leave message.

}

listen(integer ch,string name,key id, string str )
{
//llSay(0,guestName);//for test
//llSay(0,(string)llStringLength(guestName));

if( llStringLength(guestName) <= (oneLineLimit-6) )//because " said:"has 5 charactors
{
llMessageLinked(LINK_SET,LM_NAME,guestName,"");
}

else
{

}

if( llStringLength(str) <= messageLimit )
{
llMessageLinked(LINK_SET,LM_MESSAGE,str,"");

}

else
{
llSay(0,"OOPS!!Your message's charactors are over 200.Please reduce your message and say it again.Thanks!!");
}

llResetScript();

}
}

顯示字元的物件(這個有分顯示Name和訊息,但程式碼幾乎一模一樣,只是物件名稱不太一樣而已,該程式是顯示名字的,所以在Default那的截取是Name,不是M):

////////////////////////////////////////////
// XyText v1.2 Script (5 Face, Single Texture)
//
// Written by Xylor Baysklef
//
// Modified by Kermitt Quirk 19/01/2006
// To add support for 5 face prim instead of 3
//
////////////////////////////////////////////

/////////////// CONSTANTS ///////////////////
// XyText Message Map.
integer DISPLAY_STRING = 204000;
integer DISPLAY_EXTENDED = 204001;
integer REMAP_INDICES = 204002;
integer RESET_INDICES = 204003;
integer SET_CELL_INFO = 204004;
integer SET_FONT_TEXTURE = 204005;
integer SET_THICKNESS = 204006;
integer SET_COLOR = 204007;

// This is an extended character escape sequence.
string ESCAPE_SEQUENCE = "\\e";

// This is used to get an index for the extended character.
string EXTENDED_INDEX = "12345";

// Face numbers.
integer FACE_1 = 3;
integer FACE_2 = 7;
integer FACE_3 = 4;
integer FACE_4 = 6;
integer FACE_5 = 1;

// Used to hide the text after a fade-out.
key TRANSPARENT = "701917a8-d614-471f-13dd-5f4644e36e3c";
///////////// END CONSTANTS ////////////////

///////////// GLOBAL VARIABLES ///////////////
// This is the key of the font we are displaying.
key gFontTexture = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
// All displayable characters. Default to ASCII order.
string gCharIndex;
// This is the channel to listen on while acting
// as a cell in a larger display.
integer gCellChannel = 1;
// This is the starting character position in the cell channel message
// to render.
integer gCellCharPosition = 0;
// This is whether or not to use the fade in/out special effect.
integer gCellUseFading = FALSE;
// This is how long to display the text before fading out (if using
// fading special effect).
// Note: < 0 means don't fade out.
float gCellHoldDelay = 1.0;
/////////// END GLOBAL VARIABLES ////////////

ResetCharIndex() {
gCharIndex = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
// \" <-- Fixes LSL syntax highlighting bug.
gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
gCharIndex += "\n\n\n\n\n";
}

vector GetGridOffset(integer index) {
// Calculate the offset needed to display this character.
integer Row = index / 10;
integer Col = index % 10;

// Return the offset in the texture.
return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
}

ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
// Set the primitive textures directly.

// <-0.256, 0, 0>
// <0, 0, 0>
// <0.130, 0, 0>
// <0, 0, 0>
// <-0.74, 0, 0>

llSetPrimitiveParams( [
PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
]);
}

RenderString(string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );

// Use these grid positions to display the correct textures/offsets.
ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}

RenderWithEffects(string str) {
// Get the grid positions for each pair of characters.
vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );

// First set the alpha to the lowest possible.
llSetAlpha(0.05, ALL_SIDES);

// Use these grid positions to display the correct textures/offsets.
ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); // Now turn up the alpha until it is at full strength.
float Alpha;
for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
llSetAlpha(Alpha, ALL_SIDES);
// See if we want to fade out as well.
if (gCellHoldDelay < 0.0)
// No, bail out. (Just keep showing the string at full strength).
return;
// Hold the text for a while.
llSleep(gCellHoldDelay);
// Now fade out.
for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
llSetAlpha(Alpha, ALL_SIDES);
// Make the text transparent to fully hide it.
llSetTexture(TRANSPARENT, ALL_SIDES);
}

RenderExtended(string str) {
// Look for escape sequences.
list Parsed = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
integer ParsedLen = llGetListLength(Parsed);

// Create a list of index values to work with.
list Indices;
// We start with room for 5 indices.
integer IndicesLeft = 5;

integer i;
string Token;
integer Clipped;
integer LastWasEscapeSequence = FALSE;
// Work from left to right.
for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
Token = llList2String(Parsed, i);

// If this is an escape sequence, just set the flag and move on.
if (Token == ESCAPE_SEQUENCE) {
LastWasEscapeSequence = TRUE;
}
else { // Token != ESCAPE_SEQUENCE
// Otherwise this is a normal token. Check its length.
Clipped = FALSE;
integer TokenLength = llStringLength(Token);
// Clip if necessary.
if (TokenLength > IndicesLeft) {
Token = llGetSubString(Token, 0, IndicesLeft - 1);
TokenLength = llStringLength(Token);
IndicesLeft = 0;
Clipped = TRUE;
}
else
IndicesLeft -= TokenLength;

// Was the previous token an escape sequence?
if (LastWasEscapeSequence) {
// Yes, the first character is an escape character, the rest are normal.

// This is the extended character.
Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];

// These are the normal characters.
integer j;
for (j = 1; j < TokenLength; j++)
Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
}
else { // Normal string.
// Just add the characters normally.
integer j;
for (j = 0; j < TokenLength; j++)
Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
}

// Unset this flag, since this was not an escape sequence.
LastWasEscapeSequence = FALSE;
}
}

// Use the indices to create grid positions.
vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );

// Use these grid positions to display the correct textures/offsets.
ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
}

integer ConvertIndex(integer index) {
// This converts from an ASCII based index to our indexing scheme.
if (index >= 32) // ' ' or higher
index -= 32;
else { // index < 32
// Quick bounds check.
if (index > 15)
index = 15;

index += 94; // extended characters
}

return index;
}

integer LM_NAME=100;
integer LM_MESSAGE=101;

default
{
state_entry()
{
// Initialize the character index.
ResetCharIndex();

}

link_message(integer linknum, integer num, string str, key id)
{
//llSay(0,str);//for test
//llSay(0,(string)num);//for test

if(num==LM_MESSAGE)
{
//llSay(0,str);//for test

//llSay(0,llList2String( llParseString2List( llGetObjectName(),[],["Name"] ) ,1));//for test

integer x= (integer)llList2String( llParseString2List( llList2String( llParseString2List( llGetObjectName(),[],["M"] ) ,1),["-"],[]) ,0);

integer y=(integer)llList2String( llParseString2List( llList2String( llParseString2List( llGetObjectName(),[],["M"] ) ,1) ,["-"],[]) ,1);

//llSay(0,"x="+(string)x+",y="+(string)y);//for test

integer indexHead=(x-1)*5+(y-1)*20;
integer indexTail=indexHead+4;

RenderString( llGetSubString(str,indexHead,indexTail) );

}
}
}


  總之,差不多就是這樣,我有在Sand Box同學做的妙手回春那裡放置,我想有興趣的同學應該可以直接買來修改。而這程式碼最重要的功能是由Sloode提供的SloodleToolbar裡我抓出來的程式寫成的。所以並不完全是我做的,只是他開放大家可以修改而已……

  我想這個做出來後,想必有非常多的應用,比如把網頁上留言板的留言利用HTTPRequest傳進Second Life,並顯示,當然也可以做更多比我做的留言板還要好的功能,比如記錄每個留言者的內容寫入notecard(對於notecard程式我還沒碰過,不過我想應該可以),然後還有換頁功能……等

  當然類似的想法可能還有小畫家(利用touch event和更改texture畫出直線)……等,或許各位專題要是想不到做什麼,可以往這些方向去想……

這就是Sloodle提供的Sloodle Toolbar

1 則留言:

Nil;Short 提到...

注意
該篇文章是還沒看到這個連結(http://lslwiki.net/lslwiki/wakka.php?wakka=LibraryXyText12)時所撰寫的,該連結有全部的程式碼。