| In this section, you
will learn how to animate multiple prims, smoothly and realistically.
|
In this example, we are going to use 3 scripts total. One of those scripts you have already loaded in the fins. The next script will learn the movements we teach the whale. We will save those motions in a notecard. The third script will play back those movements. The only script we will end up with needing is the the one that plays back the motion, and the notecard.
This must be the last step you do when making a whale, or any other animal, robot, or movable thingamabob. You will not be able to unlink and re-link the whale after you have finished teaching the script to play back motion. The scripts will remember each prim by number. Re-linking the prims in a different order will lead to a real mess! Trust me, I know.
Copy the following script into the root prim. Name it "Prim Learning script". You will also need a notecard in your root prim. Name this notecard to "Movement". An empty note card is all you need.
// Prim position root script // Put this script in the root prim. Put the child script in all prims you wish to move. // touch the script to start recording // Reset - wipe out all recording. // Name - name a new recording // Pause - insert a 1 second pause // PlayBack - play back the current animation // RemoveAll - removel all child scripts // Record - store a new set of child prim positions //
The comments show you what the user Interface looks like:

First, you must set up the global variables:
integer debug = 0; // notecard reading integer iIndexLines; integer i = 0; integer move = 0; // N movements rea from the notecard string NOTECARD = "Movement"; // the notecard key kNoteCardLines; // the key of the notecard key kGetIndexLines; // the key of the current line //communications integer linkchannel = 5001; // for recording purposes integer dialogchannel ; // dialog boxes integer playchannel = 50003; // the playback channel integer nPrims; // total number of prims integer PrimsCounter = 0; // how many have checked in integer timercounter = 0; // how many seconds have gone by integer wantname; // flag indicating we are waiting for a name to be chatted // last heard prim params vector primpos; rotation primrot; // the list of coords list masterlist; // the list of coordinates for all child prims string curranimation; // the name of the current animation list Menu; // stoareg for the current menu integer STRIDE = 6; // the size of the data we store integer Runtime; // flag that we are in running mode vs learning mode
There are several subroutines needed. The following two will strip any extra white space to the left or right of data read from a note card.
string strip( string str)
{
return llStringTrim(str, STRING_TRIM);
}
string Getline(list Input, integer line)
{
return strip(llList2String(Input, line));
}
DumpBack() will print the coordinates to chat. You must copy and past the chat into a notecard because Second Life cannot write a notecard.
DumpBack ()
{
integer i;
integer max = llGetListLength(masterlist); // the number of recorded items
integer flag = 0;
for (i = 0; i < max; i+= STRIDE)
{
string aniname2 = llList2String(masterlist,i);
curranimation = aniname2;
integer primnum2 = llList2Integer(masterlist,i+1);
vector sprimpos2 = llList2Vector(masterlist,i+2);
rotation sprimrot2 = llList2Rot(masterlist,i+3) ;
llOwnerSay("|"+ aniname2 + "|" + (string) primnum2 + "|" + (string) sprimpos2 + "|" +
(string) sprimrot2);
flag++;
}
if (! flag)
llOwnerSay("No recording!" );
}
PlayBack() will animate the prim set when you click the Playback button
PlayBack (string name)
{
integer i;
integer max = llGetListLength(masterlist); // the number of recorded items
for (i = 0; i < max; i+= STRIDE) // look at each animation, they could be scattered
{
string aniname2 = llList2String(masterlist,i);
if (aniname2 == name)
{
integer primnum = llList2Integer(masterlist,i+1);
vector sprimpos = llList2Vector(masterlist,i+2);
rotation sprimrot = llList2Rot(masterlist,i+3) ;
string msg = llList2String(masterlist,i+4);
string UUID = llList2String(masterlist,i+5);
if (llStringLength(msg) > 0)
llSay(0,msg);
if (llStringLength(UUID) > 0)
llPlaySound(UUID,1.0);
if (primnum < 0)
{
llSleep(-primnum);
}
else
{
sprimrot /= llGetRot(); // Add in the local rot
if (primnum !=0)
llSetLinkPrimitiveParamsFast(primnum,[PRIM_POSITION,sprimpos,PRIM_ROTATION,sprimrot]);
else
llSetPrimitiveParamsFast([PRIM_POSITION,sprimpos,PRIM_ROTATION,sprimrot]);
}
}
}
}
MakeMenu() builds the dialog ox menu system. Any named animations will be added to the menu so you can click them and preview them.
MakeMenu()
{
list amenu = ["Reset","Record","PlayBack","Name","Dump","RemoveAll","Pause"];
amenu += Menu;
llDialog(llGetOwner(), "Pick a command",amenu,dialogchannel);
}
The default event is called when the script resets. Experts may notice that this script does not have an llResetScript() in it when it is rezzed. This allows you to use this script for playback and creation of notecards. Instead, we re-establish the dialog channel listener, which is shut off when stored in inventory.
default
{
on_rez(integer param)
{
llListen(dialogchannel,"","","");
}
state_entry is called when the script is first reset. We save the number of prims to compare with later, start the note card reader, and pick a channel to chat to the user on
state_entry()
{
nPrims = llGetNumberOfPrims();
llOwnerSay(" Total Prims = " + (string) nPrims);
kNoteCardLines = llGetNumberOfNotecardLines(NOTECARD);
kGetIndexLines = llGetNotecardLine(NOTECARD,0);
dialogchannel = (integer) (llFrand(100) +600);
llListen(dialogchannel,"","","");
llMessageLinked(LINK_SET,0,"Reset","");
}
Each time the server reads a line from the notecard, it will fire the event 'dataserver'.
// read notecard on bootup
dataserver(key queryid, string data)
{
if (queryid == kNoteCardLines)
iIndexLines = (integer) data;
if (queryid == kGetIndexLines)
{
if (data != EOF)
{
Each line is fetched with llGetNotecardLine()
queryid = llGetNotecardLine(NOTECARD, i);
The line was delimited in the note card with the pipe "|"
character. The llParseString2List() system call
is used to make a list from the data. This makes it simple to pick each
parameter out of the list with llList2String(). I also call the
subroutine Getline() to strip out the white space and covert what is
left into integers or floats.
A blank line will have prim num of undef. If there is a linked prim
num, there may be a sound to play or text to chat.
I then add the items read from the notecard onto the play list.
The last part is to actually animate the prim that we have read. The
new command llSetLinkPrimitiveParamsFast does the rotation and movement
all at once (well almost), with no delays.
If you touch the prim, we will bring up the dialog box menu :
When you touch the dialog box entry, or type text into chat, then the
listen() event is fired. Each of these if statements will handle a different
button.
Messages from other scripts are sent to the animator by llMessageLinked.
If a message is sent on the 'playchannel', the script will animate the
listed animation. A simple script , such as a person sensor,
collision detector, or other trigger can then play back any recorded
animation.
The other possibility is that you are teaching the script new movements.
Messages that arrive on the 'linkchannel' are stored in the master animation
list.
Clicking 'Record' will set off a flurry of Link Messages. This timer
captures the number of expected messages and displays the total as they
arrive.
Now that you have you script running, click the whale. You should
get a menu with several commands:
The menu buttons have the following operation:
Reset - Wipe out all recordings
Record - remember all child prim positions
PlayBack - playback all prim positions
Name - give a name to the current animation
Dump - print all animations to chat. You will need to copy and
paste this into the note card "Movement"
RemoveAll - deletes all scripts in child prims related to motion learning
Pause - insert a 1 second pause command into the note card
The first thing you must do is to click 'Reset'. This step makes
the script forget any values it may have read at start-up. This will
wipe out any RAM-based note card data. The note card will
still be inside our object and it will not be erased.
Click the 'Name' button. The system will respond with "Type the
current animation name on channel /601" or some other random channel
number. Type in main chat the words '/60x up'.
This will name the first animation.
Now you can edit your whale and position the flukes, tail, and fins
to the 'up' position. Position all child prims, then select
the Menu item 'Record'. You can repeat this step to move our fins
in small increments, or just move them once, insert a pause, and so
on.
When finished, click 'PlayBack' to play back the animation.
The whale script needs two animations, 'up', and 'down'.
Click the Name button again, type in /60X down', and move the whale
tail and prims for the tail down position. Click Record.
You can play back the 'down' animation and the up animation by clicking
the new buttons that appear in the menu. If you make a mistake,
just click Reset, and start over.
Once you are satisfied with the up and down positions, click 'Dump'.
A long list of our edits will be printed into chat. You must copy
this chat and paste it into the movement notecard. You can reset
the script and watch it load the notecard. each part of the fish will
move and rotate into position. This will happen slowly because
of the very slow way that Second Life reads note cards. Once it
has loaded, you can click the whale, click 'Name' and name an animation
('up' or 'down'), then click Playback to watch it move.
Option:
There are two options for the notecard: chatted text, and play a sound.
Each line of the notecard consists of the following items:
junk | prim number | position | rotation | Chatted text | UUID or sound
file name|
A sample looks like this:
You can make the whale speak and play sounds by adding the text or sound
UUID to the notecard.
Chatted text:
To add chatted text, add a pipe and text like this: |text||
to the end of any line. When the script runs that line of animation,
it will chat the word 'text' into main chat. You can change the
next script to chat this command into a private channel, and control
objects, such as a bridge that can raise, or a boat that can toot when
the whale passed by the boat.
Sounds:
If you add a UUID of a sound file, or add the sound file to the root
prim, then you can play them on command. Just put them at the
end of the chat text: |toot toot|b5af85b9-8b85-43ca-842a-f36a64d648a2|
with a pipe at the end
When you are totally satisfied with the whale flippers, click 'RemoveAll'
to finish and shut down all child scripts. You can also delete the Prim
Learning script.
Next - > Part 5-
teaching the fish to swim - putting
it all together
Back to the Best Free Tools in Second Life and OpenSim.
list lLine = (llParseString2List(data, ["|"], []));
string junk = llList2String(lLine,0);
string aniname = llList2String(lLine,1);
integer Num = (integer)Getline(lLine,2);
vector Pos = (vector) Getline(lLine,3);
rotation Rot = (rotation) Getline(lLine,4);
string msg = llList2String(lLine,5);
string UUID = llList2String(lLine,6);
if(Num>=0)
{
if (llStringLength(msg) > 0)
llSay(0,msg);
if (llStringLength(UUID) > 0)
llPlaySound(UUID,1.0);
masterlist += [aniname];
masterlist += [Num];
masterlist += [Pos];
masterlist += [Rot];
masterlist += [msg];
masterlist += [UUID];
Rot /= llGetRot();
llSetLinkPrimitiveParamsFast(Num,[PRIM_POSITION,Pos,PRIM_ROTATION,Rot]);
move++;
}
i++;
integer InitPerCent = (integer) llRound(( (i+1) / (float) iIndexLines) * 100);
llSetText("Initialising... \n" + (string) InitPerCent + "%" , <1,1,1>, 1.0);
if (InitPerCent == 100)
llSetText("" , <1,1,1>, 1.0);
kGetIndexLines = llGetNotecardLine(NOTECARD,i);
}
else
{
llOwnerSay("initialized with " + (string) move + " movements");
llSetText("" , <1,1,1>, 1.0);
llMessageLinked(LINK_THIS, playchannel, "bird", "");
}
}
}
touch_start(integer total_number)
{
if (! Runtime && llDetectedKey(0) == llGetOwner())
{
MakeMenu();
}
}
listen( integer channel, string name, key id, string message )
{
if (channel == dialogchannel)
{
if (message == "Reset")
{
masterlist = [];
MakeMenu();
}
else if (message =="Pause")
{
masterlist += [curranimation];
masterlist += [-1]; // pause 1 second
masterlist += [<0,0,0>]; // null vector
masterlist += [<0,0,-0,1>]; // null rotation
masterlist += ["Pause"];
masterlist += [""];
MakeMenu();
}
else if (message == "RemoveAll")
{
llMessageLinked(LINK_SET,0,"Remove","");
Runtime++;
}
else if (message == "Record")
{
PrimsCounter = 0;
timercounter = 0;
llSetTimerEvent(1.0);
llMessageLinked(LINK_SET,0,"Set","");
MakeMenu();
}
else if (message == "Name")
{
llOwnerSay("Type the current animation name on channel /" + (string) dialogchannel);
wantname++;
MakeMenu();
}
else if (message == "PlayBack")
{
PlayBack(curranimation);
MakeMenu();
}
else if (message == "Dump")
{
DumpBack();
MakeMenu();
}
else if (wantname)
{
curranimation = message;
MakeMenu();
Menu += [message];
llOwnerSay("Recording is ready for animation '" + curranimation + "'");
llOwnerSay("Position all child prims, then select the Menu item 'Record'.
When finished, click 'PlayBack' to play back the animation, or click 'Name'
to record a new animation,
or click 'RemoveAll' to finish and shut down all child scripts");
wantname = 0;
PrimsCounter = 0;
llMessageLinked(LINK_SET,0,"All","");
timercounter = 0;
llSetTimerEvent(1.0);
}
else
{
if (debug) llOwnerSay("Possible Animations:" + llDumpList2String(Menu,","));
if (llListFindList(Menu,[message]) > -1)
PlayBack(message);
}
}
}
link_message(integer sender_num, integer num, string message, key id)
{
if (num == playchannel)
{
if (debug) llOwnerSay("playback animation " + message);
PlayBack(message);
}
else if (num == linkchannel)
{
PrimsCounter++;
list my_list = llParseString2List(message,["|"],[""]);
if (debug) llOwnerSay(llDumpList2String(my_list,","));
string sprimpos = llList2String(my_list,0);
string sprimrot = llList2String(my_list,1);
primpos = (vector) sprimpos;
primrot = (rotation) sprimrot;
if (llStringLength(curranimation) > 0)
{
masterlist += curranimation;
masterlist += sender_num;
masterlist += primpos;
masterlist += primrot;
masterlist += ""; // sounds
masterlist += "";
integer count = llGetListLength(masterlist) / STRIDE;
}
}
}
timer()
{
integer left = nPrims - PrimsCounter; // how many left to report in
if (left)
llOwnerSay((string) left + " remaining of " + (string) nPrims);
else
{
llSetTimerEvent(0.0);
llOwnerSay("Ready");
}
if (timercounter++ > 5)
{
timercounter = 0;
PrimsCounter = 0;
llOwnerSay("Giving up");
llSetTimerEvent(0);
}
}
}
How to animate a multi-prim creature

[21:01] Object: |up|9|<-2.24379, -1.58608, -8.90106>|<-0.47732, -0.23147, 0.70642, 0.46857>|hello!|hello.wav
[21:01] Object: |up|10|<-2.46843, 1.23117, -8.86807>|<-0.13555, -0.43806, 0.53353, 0.71069>