You are currently browsing the category archive for the ‘LSL Script’ category.
A Little History
Second Life has long had a system for displaying video on a prim. This is the parcel-media system, whereby you can supply a URL for a video feed, and the video will be displayed on an assigned texture (if you have never seen this before, the set-up for it is on the last tab of the About Land dialog). Its main restriction has been (and still is) that only one video can be displayed on each parcel of land. You can have any number of prims displaying the video, but they will all display the same video.
It has always been possible to display output from a web-site to the parcel media stream, although it was never easy. If you are interested, it involves creating a CGI script on a web-site to generate an image. By then pointing the parcel-media URL at this script, the image will be displayed on any prim which uses the parcel-media texture.
I experimented with this myself a little while ago, and got a system up and running which would display text which was ‘chatted’ by an avatar. I had ideas about creating some sort of live broadcast system. Unfortunately, the Quicktime vulnerability then appeared, and for some time anyone with any sense kept parcel-media switched off. I stopped experimenting at that point, and didn’t go back to it even after the vulnerability was fixed.
Recent Changes
The more recent versions of Second Life include the ability to directly display a web-page via the parcel-media. Point the parcel-media URL at a web-page, and the page will be displayed on the parcel-media texture.
This brings us a step closer to the long-promised HTML-on-a-prim, but we aren’t quite there yet. For a start, this system only applies to parcel-media, and hence suffers from the same restrictions that parcel-media suffers from. There are also some quirks in the way in which the web-pages are displayed.
The main problem that I have found is that the pages seem to rendered at a fixed size (it looks to be something like a 1024 x 1024 area). If the webpage is too large to fit this, scroll-bars will appear. Of course, because the page is essentially just displayed as an image, these scroll-bars are inactive in Second Life. It also means that you have to display the page on a fairly sizeable prim in order for a normal web-page to be readable. It would have been nice to have the ability to display only part of the web-page, with it sizing to fit the prim.
Implementing a Broadcaster
So much for the background. Now, how can we make use of this? Obviously, we can just display a web-page, but there other things that can be done, such as creating genuinely active text. That’s what I want to demonstrate in this post. It is going to be rather simplistic and limited, but it should be quite easy to extend the basic idea into something more sophisticated and useful.
To make particular example work, you will need a website that handles PHP, and that you can upload files to. You will also need to own or rent a parcel of land that you can change the parcel-media on (obviously!).
Let’s start with the PHP script (if you are more familiar with other web-page scripting languages, it should not be difficult to adapt this script):
<html> <head></head> <style>body { font-size: 64pt; font-family: Verdana, sans-serif }</style> <body> <?php // Get the text to display. $contents = stripslashes($_GET["contents"]); // Only works with PHP compiled as an Apache module. $headers = apache_request_headers(); $ownerKey = $headers["X-SecondLife-Owner-Key"]; // Output the text. echo("<p>".$contents."</p>\n"); ?> </body> </html>
This script expects to be passed a ‘comments’ parameter containing the text to be displayed. For example, assuming you have saved the script as broadcast.php, the following would be used to call the script:
broadcast.php?contents=This%20is%20a%20test.
This would display a web-page with the line “This is a test.”.
A couple of other points: the code above includes a line to read the owner key, but doesn’t make any use of it. In a real situation you would probably want to use this as the basis for some kind of security (remember that anyone with a browser can call this script!). There are also a number of other headers which are available — see the LSL Wiki for more information about them.
Also note that I have set the font-size to a rather large 64 points! This is so that the line of text will show up at a reasonable size on our display.
Now for the Second Life side.
For this example we will set up a script that will listen on a specified channel, and will use our web-script to display anything which is chatted on that channel. We will also set up a timer that will automatically clear the text and replace it with a default after 30 seconds.
In this example I am using a script on my own website — you should replace the URL with the appropriate path for your own site. You can make use of the script on my website if you want to just try things out, but it will display my own logo, and is not guaranteed to stay on the website for long, as it is only a test.
string text; string baseUrl = "http://ejournal.cyberias.net/broadcast.php?contents="; integer anyoneCanUse = TRUE; key avatar; integer handle; integer onChannel = 1234; init() { text = llEscapeURL("Chat some text on channel /" + (string)onChannel); string url = baseUrl + text; llParcelMediaCommandList([PARCEL_MEDIA_COMMAND_URL, url, PARCEL_MEDIA_COMMAND_PLAY, PARCEL_MEDIA_COMMAND_LOOP]); } send() { string url = baseUrl + text; llParcelMediaCommandList([PARCEL_MEDIA_COMMAND_URL, url]); } default { state_entry() { llSetTimerEvent(30.0); handle = llListen(onChannel, "", avatar, ""); } listen(integer channel, string name, key id, string message) { if (channel == onChannel) { llSetTimerEvent(0.0); text = llEscapeURL(message); send(); llSetTimerEvent(30.0); } } timer() { text = llEscapeURL("Chat some text on channel /" + (string)onChannel); send(); } state_exit() { llListenRemove(handle); llSetTimerEvent(0.0); } }
Create a prim, and apply your parcel-media texture to one face. Create a script in the prim, and replace the default contents with the above code.
Anyone who stands near the prim and chats on the specified channel will have the chat displayed on the prim (provided, of course, that they have video switched on).
Here is my own test broadcaster in action:
Where’s the point in having a blog if you can’t use for some occasional self-advertisement? Actually, some people’s blogs are solely for this purpose, so I think I can be excused (and I’ve marked this as entry 1 because I intend to more in future — you have been warned!)
I have recently completed my first significant gadget for Second Life, a functioning reproduction of a Victorian Magic Lantern. To show it off, and to make a place for selling both it and other things I might create, I’ve built a small store, Arcane Designs, next to Morgridge Mansion.
As a matter of fact, I’ve already got a second item on sale, which is my Psychic Card Table, a Second Life version of a card trick which I have seen elsewhere on the Internet. It has been on display in the Mansion for some time, as a sort of conversation-piece, but I’ve decided I might as well put it on sale.
Both items are also available from SL Exchange. You want to see them here first? All right, you’ve twisted my arm.
Links
Morgridge Mansion
If you have Second Life installed you can use this link to teleport to Morgridge Mansion. It will actually drop you at the hub. Walk north-west to get to the Mansion itself — it isn’t very far.
As with all my script posts, I am assuming here that you already know the basics of how to create and edit prims and scripts.
I’ve already covered the rotation option for animating textures, in my post about creating a simple rippling water texture. In this post, however, I want to look at the other animation options, and give some examples. The only one I’m not going to cover is the scaling option, which works in a similar way to the rotation option, but which I have never found a real use for.
Let’s start with what most people think of in terms of texture animations, which is where the animation displays a series of ‘frames’. As a simple example, here is an alternative way of creating a rippling water effect, this time using an animation. To see this in action, rez a cube, and create a new script for it. Replace the contents of the script with the following:
default { state_entry() { llSetTexture("349b2fec-0f7e-bd5f-5bcc-7090d27ae0fb", ALL_SIDES); llSetTextureAnim(ANIM_ON | LOOP, ALL_SIDES, 3, 3, 1, 9, 12.0); } }
As with all the examples in this post, I am taking the textures from the asset server using their UUID, so they should be available to anyone. Of course, you don’t have to do it this way – you can apply animated textures to the prim in the normal way if you want (in which case you can remove the llSetTexture call in the above example).
The texture in this example is 128 x 128, and has the frames laid out in a 3 x 3 grid (the texture was created using the Gimp drawing package). It is actually rather smaller than is necessary, and hence not very high quality. A 256 x 256 version of the texture would be better quality, and would still load reasonably quickly.
Let’s look at each of the parameters of the llSetTextureAnim function.
The first parameter is a set of flags which are combined together using an OR (the ‘|’ symbol). In my example, only two options are included: ANIM_ON, which switches the animation on (no surprise there!) and the LOOP option, which tells the animation to return to the beginning and start again once it has reached the last frame. The alternative to LOOP is PING-PONG, which still keeps the animation running continuously, but when it reaches the last frame it then plays the frames in reverse until it gets back to the first frame, and so on.
If you don’t specify one or other of LOOP or PING-PONG the animation will play through just once and then stop (it will redisplay the first frame, though). We’ll see a possible use for this later.
The other possible flags for this parameter are SCALE, ROTATE, REVERSE, and SMOOTH. As I said above, I won’t be going in to SCALE and ROTATE in this post. REVERSE simply plays the animation backwards. I’ll come back to the SMOOTH option later.
The next two parameters tell the function how the frames are laid out in the texture. In my example, they are laid out in a 3 x 3 grid, so we set both values to 3. A common alternative would be to have the frames laid out horizontally in a long strip. If this sample texture had been done that way we would have had a strip 9 frames long, so these parameters would be 1, 9.
The next two parameters tell the function which frames to display, from the first frame to the last frame. You can use this if you only want to display part of the animation (possibly you might want to display different parts at different times). In this case we want to use all the frames.
The last parameter is the speed of the animation — the higher the number, the faster the animation. It is approximately (very approximately!) in frames per second, and values between 5 and 15 are usually appropriate, though of course this depends on your animation.
Now, what about that SMOOTH option? Well, this works in a rather different way. Instead of displaying individual frame, the SMOOTH option basically scrolls the entire texture sideways. Because of this, it is possible to use this to scroll a texture which isn’t animated (in other words, a texture which isn’t actually a series of frames).
As an example, let’s create a slowly drifting starfield. Rez a cube, and size it so that it is about 4 m wide and 2 m tall. Create a new script in it, and replace the script contents with the following:
default { state_entry() { llSetTexture("43019701-cf5c-d484-3b35-3fb59fe895b4", ALL_SIDES); llSetTextureAnim(ANIM_ON | LOOP | SMOOTH, ALL_SIDES, 1, 2, 1, 1, 0.02); } }
Notice that although the texture is not animated, we have specified a Y frame count of 2. This might seem rather odd, and needs some explanation. As soon as you apply llSetTextureAnim, the script will override whatever X and Y repeat values that you have specified, and instead will size the texture to match the X and Y frame count that you have specified, stretching it as necessary.
In order to make the texture fill the X direction without stretching, the Y frame-count has been specified as 2, making the script treat the texture as a two-frame animation. Because the start and end frames are both 1, it only displays one frame, which in this case works out as the top half of the texture, with the result that the texture is scaled correctly for us. This is not really an efficient way of doing things! Really, an animated texture needs to have the same X:Y ratio as the prim face that you are going to use it on.
Next, something more useful. Let’s see how to use texture animation with Second Life’s waterfall textures, and create a simple waterfall.
Start by rezzing a cube, and size it so that it is 2 m wide, 4 m tall, and 0.2 m deep. On the Texture tab, set the rotation to 90.0. This is because animations which use SMOOTH, as we are going to do, always slide along the X axis, so we need to rotate the texture to make our water flow vertically.
Create a new script in it, and replace the script contents with the following:
default { state_entry() { llSetTexture("af8c86bd-c377-c331-7476-58abeb7af8fc", ALL_SIDES); llSetTextureAnim(ANIM_ON | LOOP | SMOOTH, ALL_SIDES, 1, 2, 1, 1, 0.5); } }
By itself, this should look reasonably effective. We can make it better, though. Take a copy of this prim, shifting it so that it is a little bit in front of the original prim. Edit the script in this copy, and replace it with the following:
default { state_entry() { llSetTexture("49649c94-f720-6d0f-2246-49cc1835284f", ALL_SIDES); llSetTextureAnim(ANIM_ON | LOOP | SMOOTH, ALL_SIDES, 1, 2, 1, 1, 0.25); } }
This now gives us two waterfall animations, running at different speeds. The result is very effective.
Near the start of this post I mentioned that if you had neither LOOP nor PING-PONG in the animation flags the animation would run through just once. Here is an example of this. It is a ‘LOCKED’ sign which flashes once when somebody touches it.
Rez a new cube, and size it to be 1 m wide, 0.5 m tall, and 0.5 m deep. Create a new script in it, and replace the script contents with the following:
default { state_entry() { llSetTexture("7392be5b-ce2f-eb64-6a45-a4544ec539a9", ALL_SIDES); llSetTextureAnim(ANIM_ON, ALL_SIDES, 1, 2, 1, 2, 5.0); } touch_start(integer total_number) { // Curiously, we have to switch the animation off first. llSetTextureAnim(0, ALL_SIDES, 1, 2, 1, 2, 0.0); // Now run the animation once. llSetTextureAnim(ANIM_ON, ALL_SIDES, 1, 2, 1, 2, 5.0); } }
Finally, some useful (I hope!) notes:
Finding the prim face: You’ll notice that in the above scripts I have specified ALL_SIDES as the side to apply the texture and animation. A lot of the time you will want to put the texture and animation on one side only. The easiest way to find out the side number of a prim face is to use the Advanced (or Debug) menu.
To active or deactivate the menu, hold down CTRL+ALT+d on your keyboard. The Advanced menu should appear on the main Second Life menu.
Once this menu is showing, edit the prim you want to investigate. Click the ‘Select Texture’ option, and select the face you want to check. Then hold down CTRL+SHIFT+ALT+t on your keyboard. Details of the texture will appear in the bottom-left of your screen, including the face number.
Switching off animations: If you want to switch off the animation on a texture, simply pass zero as the flag. The other parameters are irrelevant:
llSetTextureAnim(0, ALL_SIDES, 1, 1, 1, 1, 0.0);
Texture Size: remember that a texture with multiple frames can easily be very large indeed, and Second Life still has to load the entire texture (no, it doesn’t load it frame by frame!). Creating an effective animation using a minimum number of frames is a skill in its own right.
The LSL Wiki has additional useful information: http://www.lslwiki.net/lslwiki/wakka.php?wakka=llSetTextureAnim
Anyone who has tried creating an opening door in Second Life will no doubt have found that it is surprisingly tricky. In this post, I’ll describe a way of doing it that lets you build a door which opens, can be linked to other objects (such as a building!), and which will still function correctly when the building that it is linked to is rotated — if you have attempted to create doors before, you will no doubt already be aware of the problems that can arise with each of
these.
Creating a reliable opening door requires a mixture of a specially constructed prim, and a script. Let’s start with the prim.
Create a cube block.
Set the Start Cut parameter to 0.125 and the End Cut parameter to 0.625. You will notice that the pivot centre of the door is now at one edge of the visible part, instead of directly in the middle. This is the key (!) to the way that the door will work — we can now rotate the door very simply, and it will rotate around this hinge point.
Resize the result to something suitable for a door, making sure that you keep the pivot centre on the upright edge of the prim.
Note that when you texture the door, the number of horizontal repeats will need to be set to 2, because only half of the prim is visible. Once you apply a texture you will this will become obvious, as the following pictures should make clear:
Now for the script. To make sure that the door works correctly regardless of the angle to which it might have been rotated, we must use the Local rotation rather than the global rotation. We will use a variable to keep track of whether the door is open or closed — the only thing to be careful about here is that you must make sure the door is in the closed position at any time when the script is reset.
integer isOpen = FALSE; float rotateBy = 0.8; openclose() { if (isOpen) { llSetLocalRot(llEuler2Rot(<0,0,-rotateBy*PI_BY_TWO>)*llGetLocalRot()); isOpen = FALSE; } else { llSetLocalRot(llEuler2Rot(<0,0,rotateBy*PI_BY_TWO>)*llGetLocalRot()); isOpen = TRUE; } } default { touch_start(integer total_number) { openclose(); } }
Close the build dialog, and touch the door a couple of times — it should open and close sensibly.
It is now possible to link the door to a building, and the door should continue to work properly. The only thing you must be careful about is that door must not be the root prim. If the door is made the root prim, when it rotates it will rotate the rest of the building with it! If the door is a child prim, it will only rotate itself.
For a more sophisticated door script, check out the Deluxe Door on the LSL Wiki site:
An alternative is the sliding door. This doesn’t need a special prim — just size a normal cube into the size you want for the door, and use the script that you will find below (but read the rest of this first).
The slideBy amount is the amount by which the door will slide when it opens. Usually you will want this to be something a little smaller than the width of the door. This should leave part of the door visible, assuming, of course, that it is sliding into another prim.
We don’t need to use local co-ordinates for this door — the position is always local. However, you need to check which direction the door will slide in. This will normally be either the X or Y axis, depending on which is the width of the door. Be careful about rotating the door after adding the script, but before linking the door.
For example, here is a rough draft of a door. As you see, it is aligned so that it should slide along the X-axis (the red arrow).
If I rotate the door through 90 degrees, it now needs to slide along the Y-axis (the green arrow) and I will need to change the script accordingly.
Once the door has been linked in place, you can rotate the entire object, and the door will still move in the correct direction in relation to the rest of the object.
Here’s the script, set to move along the X-axis.
integer isOpen = FALSE; vector originalPos; float slideBy = 1.5; init() { originalPos = llGetLocalPos(); vector size = llGetScale(); // Set up the amount to move by. Use size.y instead of size.x // if you want to slide along the y-axis. slideBy = size.x - 0.2; } openclose() { if (isOpen) { llSetPos(originalPos); isOpen = FALSE; } else { llSetPos(<originalPos.x + slideBy, originalPos.y, originalPos.z>); // To move alon the y-axis, use this instead: // llSetPos(); isOpen = TRUE; } } default { state_entry() { init(); } on_rez(integer param) { init(); } touch_start(integer total_number) { openclose(); } changed(integer change) { // When the links change, reset the script // so that we pick up the changes. if (change & CHANGED_LINK) { llResetScript(); } } }
Sometimes it is useful to be able to change or apply textures through a script. Imagine, for example, a button or switch where you want to change the appearance when the user clicks on it.
There are a number of functions in the LSL library for dealing with textures. In this post I will cover the most basic of them. I’ll go into more details in subsequent posts.
Applying a Texture
Applying a texture to a prim is probably the simplest thing that can be done when scripting textures. You can use the llSetTexture function to do this, which simply needs the texture, and the side to apply the texture to. I’ll discuss prim sides a bit later, but for the example I’ll use the ALL_SIDES constant, which will apply the texture to all sides of the prim.
There are two different ways of specifying the texture. You can either use the name of the texture, or you can use the asset UUID of the texture.
To use the name, the texture must be included in the prim’s contents (just drag the texture from your inventory into the contents list of the prim).
Assume we have a texture called “test”. This script will apply the texture to all sides of the prim when the prim is touched:
default { touch_start(int number) { llSetTexture("test", ALL_SIDES); } }
Alternatively, we can specify the asset UUID. To find the UUID, right-click on the texture in your inventory, and select the Copy Asset UUID option. You can then paste the result into the script, like so:
default { touch_start(int number) { llSetTexture("3c8f1c70-671a-4cb6-9f06-58b95c77e6a6", ALL_SIDES); } }
The advantage with using the UUID is that the texture does not need to be in the prim’s inventory, it only needs to exist somewhere in SL’s asset server.
Note that the asset UUID in the above code is a real one, and will get you a “test card” pattern that you can use for testing offsets (see below).
Aside: What happens if you upload a texture into your inventory, get the UUID for it, then delete the texture? Will the texture vanish from Second Life altogether? No, it won’t. Once a texture is uploaded, it is permanently added to the asset server, and deleting it from your inventory has no effect on it — you simply won’t be able to refer to it by name any longer. It will still exist on the server, and you can still use the UUID to refer to it in a script.
Sides
Suppose you want to apply a texture to just one side of a prim. To do this you simply specify the side number:
llSetTexture(“test”, 1);
Ah, but how do you know which side has which number? That’s the tricky bit. Assume that you have just rezzed a cube, and haven’t rotated it in any way. The sides will be numbered as follows:
top: 0
side with lowest y: 1
side with highest x: 2
side with highest y: 3
side with lowest x: 4
bottom: 5
If you hollow the cube, side 5 is the inside (yes, all four interior sides count as a single side), and the bottom becomes side 6.
Working out the side numbers for a rotated cube, or for a more complex prim, gets rather more difficult, and can involve a lot of trial and error. A better way is to use the debug menu. If you don’t have the debug menu displayed, press CTRL+ALT+D to reveal it.
To get information about one side of a prim, tick the ‘Select Texture’ option in the build dialog, select the prim side that you want to know about, then press CTRL+SHIFT+ALT+T. Information about the prim side will be displayed, including the side number.
Scaling and Offsetting
Once a texture is applied, you can scale and offset it using the llScaleTexture and llOffsetTexture functions.
llScaleTexture(0.5, 2.0, ALL_SIDES);
llOffsetTexture(0.5, 0.0, ALL_SIDES);
The horizontal and vertical parameters taken by these functions are exactly the same as those on the texture tab of the build dialog, so you can use the build dialog to help find the appropriate values.
If you have already worked with textures you will know that a scaling of 1.0 x 1.0 will fit the texture to the size of the prim, whereas higher values will tile the texture, and lower values will only display part of the texture.
For example, a value of 0.25 will display one quarter of the texture, stretching it to fit the prim. Which quarter of the texture is actually displayed depends on the offset, and the way in which the offset works is not immediately obvious.
Basically, the offset is from the centre of the texture. An offset of 0.0 x 0.0 will therefore centre the texture. How other offset values affect the texture is easier to explain using an actual example.
Here is the testcard texture which I mentioned above, applied initially with an offset of 0.0 x 0.0:
If you change the horizontal offset to 0.25 you get this:
Try changing the horizontal scaling to 0.25, and the horizontal offset to 0.0. This produces the following, which is actually displaying the centre of the scaled texture, showing half of one band of colour, and half of the other.
To offset it so that we are centred in a band of colour, we need to use an offset of 1/8, or 0.125. Set the horizontal offset to this value, and we get a single band.
To centre each band in turn, use the following offsets:
-0.375
-0.125
0.125
0.375
Obviously, this only works when the horizontal scale is 0.25.
Using scaling and offsetting to select just one portion of a texture can help to make more efficient use of textures, especially if you have a prim which needs to sometimes change appearance — you can pack the different images into a single texture, and select the appropriate part of the texture using the scale and offset. The alternative method is to have multiple textures, but this means that when the texture is changed, there is a delay while the new texture is loaded. Using the scale and offset method, the texture is already fully loaded, and the prim changes appearance almost instantly.
I’ll cover this more in a later post.
Links
You will find a lot of useful information about the texture-handling functions on the LSL Wiki:
LSL Wiki
In my previous blog entry about creating a HUD, I briefly mentioned that it was possible, but not necessarily easy, to have dynamic output on a HUD.
Here is a relatively simple way of doing this, along with a script that makes use of the technique. It relies on three simple facts relating to the floating text that can appear above a prim. First, this text still appears even when the prim is used in a HUD; second, it is then only visible to the wearer; and finally, the floating text can have multiple lines. We can use this to create a screen display for the HUD.
As an example, let’s create a ‘green-screen’ monitor for a HUD. Create a basic HUD window (a simple block prim), and add two more prims, one directly above the other. The upper prim will provide the background, the lower prim will generate the text.
Set the upper prim’s color to black, and size it to something suitable (the actual size depends on the number of lines of text you will be displaying, so some trial and error is probably required). Link the prims, and attach the result as a HUD. See my previous article if you need more details about creating a HUD.
Now we need a script to generate our dynamic text. The following script implements a scrolling display, and adds a line to the display every time the owner clicks the text prim (just as a demonstration). Create a new script in the lower prim, and copy the script into it, replacing the default script.
The result should look something like this:
integer linePos = 0; list lines; integer listLength; vector textColor = ; // Green init() { clear(); } clear() { lines = ["", "", "", "", ""]; listLength = llGetListLength(lines); linePos = 0; updateDisplay(); } setLine(integer lineNo, string lineText) { // If the requested line number is past the end of // the list of lines, scroll the lines. if (lineNo > listLength) { lines = llList2List(lines, 1, listLength - 1); lines += ""; lineNo = listLength - 1; } lines = llListReplaceList(lines, [lineText], lineNo, lineNo); updateDisplay(); } updateDisplay() { string output; integer i; integer count = listLength; for (i = 0; i < count; i++) { output += llList2String(lines, i) + "\n"; } llSetText(output, textColor, 1.0); } addLine(string lineText) { setLine(linePos, lineText); if (linePos < listLength) linePos++; } default { state_entry() { init(); } touch_start(integer count) { // This is just a dummy example of how to update // the display. When the owner touches the prim // we will add a message to the display. We'll // check that it was the owner who touched us, // although that isn't really necessary if this // is in a HUD. if (llDetectedKey(0) == llGetOwner()) { addLine("Touched at " + llGetTime()); } } on_rez(integer param) { init(); } }
I’ve done a number of posts now about scripting, so maybe it’s time to move on to a new subject. I’m going to assume (as I did with scripting) that you are already familiar with the basics of texturing in Second Life, and also that you are probably familiar with a suitable graphics package for creating or adjusting textures.
Recently I’ve started experimenting with drop-shadows, and on reading Iris Ophelia’s article in New World Notes I see that I am not the only one. Drop- shadows are evidently the new thing!
But what is a drop-shadow, and why would you want one? An illustration is in order. Here is a snap-shot of one of the rooms of my SL home, Morgridge Mansion:
The key thing to note is that in real life there would be an element of shadowing beneath the objects (especially the chairs and the table). Let’s add this in Second Life:
Much better. A nice bit of added realism (to be honest, I’ve slightly overdone some of the shadowing, just to make it stand out for this illustration). You’ll notice that I’ve also added a shadow behind the portrait on the wall.
So…how is it done?
Well, I don’t know how other people do it, but I’ll explain how I achieved the effect.
The first thing you need is a shadow texture. For the Mansion I created two textures, one circular, and one square. I won’t give exact details on how to do this, because they depend on which graphics package you use (personally I use The Gimp, which is free, powerful). If you aren’t familiar with creating textures, I’ve included links to my own shadow textures, which you can feel free to copy and use — see the bottom of this post.
If you want to try creating your own shadow texture, these are the steps that I took. Translate them to suit your own graphics package:
I used a 128×128 graphic (shadows don’t need a high-resolution, so this size is perfectly adequate) with transparency. I then selected an area about 3/4 of the size of the entire graphic, feathered it to about 25 pixels (you can play about with this — it will determine how quickly your shadow ‘fades out’ towards the edges), and used a blend fill of black to dark gray to fill the area. The end result (in both square and circular versions):
(To be honest, I’m not happy with the square version, which has something of an ‘X’ effect to it. More work required, methinks.)
Ok, now we’ve got our texture, how do we apply this in Second Life?
Start with a new prim — cube for square shadows, cylinder for circular shadows. Resize the Z-scale to the minimum, 0.01, and adjust the X and Y scales until the prim is slightly larger than the object that you want to shadow (if you are adjusting the prim using the ‘grab handles’ rather than entering numbers into the dialog, you probably want to scale the X/Y size first, before minimising the Z scale).
You now need a completely transparent texture. There is a transparent texture in the default library, but unfortunately it is not completely transparent, and will leave you with an ugly white edge. I’d avoid it and use your own texture. If you don’t have a fully-transparent texture, I’ve included a link to mine at the bottom of this post.
Apply the texture. Then select the ‘Edit Texture’ option from the build dialog, and select the top surface of the prim. Apply the shadow texture to this surface.
Move the prim so that it is directly beneath the object that is to be shadowed.
On the Features tab of the build dialog, adjust the transparency. The higher the transparency, the more subtle the shadow. I’ve found that a value of between 40 and 60 seems to work best. Experiment.
Link the shadow prim to the main object, and you’re done.
And finally
The transparency of the shadow prim affects the darkness of the shadow, and the best value to use depends on where the object is located. It would be handy to give the owner of the object a way of adjusting this. Here’s a simple script that does just that. Drop this script into the shadow prim. Each time the owner clicks on it, the transparency will increase by 10 (fading the shadow). When the transparency reaches 100, the next click will return it to 0 (completely black):
// Default transparency value. Change to suit. float transparency = 50.0; // Amount to change transparency on each click. float changeBy = 10.0; default { touch_start(integer count) { // Only allow the owner to change the shadow. if (llDetectedKey(0) == llGetOwner()) { // Adjust the transparency value. transparency = transparency + changeBy; if (transparency > 100.0) { transparency = 0.0; } // Apply the new value to the prim. llSetAlpha // works in reverse to the Transparency value // in the build dialog (!), so we'll subtract from // 100 to make them match. llSetAlpha(1.0 - (transparency / 100.0), ALL_SIDES); } } changed(integer change) { // Reset the script when the owner changes, // otherwise llGetOwner() might return the // wrong value. if (change & CHANGED_OWNER) { llResetScript(); } } }
Downloadable textures
This is going to be a long one. Get yourself a cup of coffee first.
What is a HUD? If you have to ask that question, you probably won’t get much from this article, but HUD is an acronym of Head-Up Display, and allows creators in Second Life to add a user-interface to gadgets.
They are created from prims just like any other user-created objects in Second Life, but they are only visible to their owner, and always appear ‘flat’ against the screen. If you’ve used any gadgets in Second Life, you will almost certainly have come across them.
For example, here is the popular blogHUD, for making blog postings from within Second Life:
When I started trying to create HUDs myself, I found that there was very little information about how to go about it, and I struggled for a while with even the simplest things. Now that I think I know what I am doing(!), I thought I would post a blog passing on my hard-won information!
In this post, I’ll show the basic steps of creating a HUD, and point out some of the pitfalls and ‘gotchas’ that you are likely to encounter along the way.
Are you sitting comfortably? Then I’ll begin…
The first thing to do, of course, is to decide what our HUD will contain. Because of the limitations of SL, it is not easy to include any kind of output in the HUD (with some ingenuity it can be done, but that is beyond the scope of this blog), so our HUD will basically be restricted to having some buttons to push.
For the sake of an example, we are going to create a HUD which allows the user to scan the area for other avatars, and to ‘poke’ (via chat) one of those avatars. Not very exciting, I know, but it is only an example!
We will need two buttons: Scan and Poke. We will also need some sort of ‘window’ for them to sit on. We could have them just floating on the screen, but that would look rather odd.
The first step is to create a sort of ‘rough draft’ of our HUD. I’ll explain why in a minute.
Let’s start with the window.
(Oh, one quick point – I am going to assume that you are fully familiar with creating/scaling/texturing prims. If you aren’t, you really shouldn’t be trying to create HUDs yet!).
Create a simple cube. Set the X scale to 0.01, so that you end up with a thin, flat board. Move it to somewhere close so that you can see it easily. If you want, you can blank the texture, though that isn’t important – we’ll deal with the texture later.
Next, let’s add a couple of buttons. Create two more cubes, setting the X scale to 0.1 again, and setting the Y and Z scale to something that approximates a sort of rectangular button shape. Place them flat against the ‘window’ board. Don’t worry about positioning them exactly yet, but you might want to colour them so that you can see them more clearly.
Link the buttons and the window (I’d suggest making the window the root prim, by selecting it last before linking, though it isn’t important for the simple HUD that we are going to make). One warning – don’t rotate this object, otherwise when you come to attach it, it might not be facing you!
Ok, now we have a primitive-looking window and buttons. Time to turn them into an actual HUD. Right-click the linked object, click ‘More’ on the pie menu, then click Attach HUD, and select Center.
If by chance you find that it is at the wrong angle (probably sideways!), you can rotate it even though it is attached. You might find it easier to adjust the numbers manually in the build dialog, rather than trying to use the Rotate tool.
Yuk. You now have a grotesque HUD, probably occupying far too much of the screen, and obscuring your view. Don’t worry, there are things we can do about it. In fact, if you are at all familiar with building stuff, I’m sure that you are already ahead of me. Hold fire, though, because there are a few things you need to know before going on.
Firstly, yes, you can edit the HUD when it is attached. This is very convenient, because an unattached HUD is very small and fiddly to work with. However, there are some limitations on what you can actually edit once the HUD is attached. This is why it is useful to do a ‘rough draft’ while the HUD is unattached and you have all the editing facilities available to you.
The most important limitation on editing an attached HUD is that you cannot unlink the prims that make up the HUD, and you cannot link new prims to the HUD. If you find that you need to add a new prim, you will have to detach the HUD (it will vanish into your inventory, and you will need to re-rez it, which is rather annoying), create and link the new prim, then re-attach the HUD.
To work on the individual elements of the HUD, you need to tick the ‘Edit linked parts’ checkbox on the Build dialog. I’ve found that this normally lets you do most things to the individual parts, although rescaling prims sometimes refuses to ‘take’. If you get that happening to you, unfortunately all you can do is drop the HUD and unlink it while you edit the offending prim.
For our simple example, rescale the entire HUD to something more suitable. You can then edit the two buttons to position them more exactly (this is much easier to do now that the HUD is attached).
Of course, it is still obscuring our view. For now, select the ‘window’ prim, blank the texture, colour it grey (or whatever you prefer), and set the transparency to something between 50 and 75.
You could do something similar with the buttons, but we need to get text onto these in some way. Because SL doesn’t yet provide text-on-a-prim, you will have to break out your favourite graphics program, and create some button textures.
Naturally, you can do something similar with the texture for your window prim, and create something fancier than the plain version that we’ve just made.
If you are feeling lazy, here are a couple of example button textures that you can use instead:
“Scan” button texture (.tga)
“Poke” button texture (.tga)
Here is the final result (I’ve done some resizing, and reattached the HUD at the top-left instead of the centre):
One final ‘gotcha’. Go back to when we originally attached the HUD, and we found that it took up large amounts of screen space. How much screen space do you imagine a prim of a given size will take up? The answer is…it depends. Most crucially, it depends on the size of the window (or the screen resolution if running full-screen) that Second Life is using. A HUD will occupy more space in a smaller window, less space in a larger window. You need to think about this when designing your hud (make the mistake of trying to create a ‘full-screen’ HUD, and it will only fit properly at one specific screen-size. Not good).
Ok, we now have our HUD designed and built…but it doesn’t actually do anything! Time to rectify that.
Select the buttons individually, and create a new script in each one.
For the Scan button, edit the script, and replace the default with the following, which does a simple scan of the surround area, and outputs the names of any avatars found (the output is done via llOwnerSay, so it is only visible to the user of the HUD):
default { touch_start(integer total_number) { llOwnerSay("Scanning..."); // Look for any avatars within 96m. llSensor("", NULL_KEY, AGENT, 96.0, PI); } sensor(integer num_detected) { integer i; while(i < num_detected) { if (llDetectedKey(i) != llGetOwner()) { llOwnerSay(llDetectedName(i)); } ++i; } } }
For the Poke button, replace the default script with this rather more elaborate one. This again does a scan of avatars in the vicinity, and then displays a dialog with their names as the buttons. When you select an avatar from the dialog, the chat message ‘[Your name] pokes [avatar name]’ is displayed.
integer dlgHandle = -1; integer dlgChannel = -9999; list avatarList = []; reset() { llSetTimerEvent(0.0); llListenRemove(dlgHandle); dlgHandle = -1; } default { touch_start(integer total_number) { llOwnerSay("Scanning..."); avatarList = []; // Look for any avatars within 10m. llSensor("", NULL_KEY, AGENT, 96.0, PI); } sensor(integer num_detected) { integer i; while((i < num_detected) && (i < 9)) { if (llDetectedKey(i) != llGetOwner()) { avatarList += [llDetectedName(i)]; } ++i; } if (llGetListLength(avatarList) > 0) { state dialog; } } } state dialog { state_entry() { // Set up a listener to detect button clicks. dlgHandle = llListen(dlgChannel, "", llGetOwner(), ""); // Start a new timer. llSetTimerEvent(30.0); // Add a 'Cancel' button. avatarList += ["Cancel"]; // Display the dialog. llDialog(llGetOwner(), "Please select an avatar.", avatarList, dlgChannel); } listen(integer channel, string name, key id, string message) { // The message parameter holds the caption of the // button that was clicked. Search the menu options // list for it. if ((channel == dlgChannel) && (llListFindList(avatarList, [message]) != -1)) { if (message != "Cancel") { llSay(0, llKey2Name(llGetOwner()) + " pokes " + message); } reset(); state default; } } timer() { reset(); state default; } }
Close the Build dialog, and your HUD should be fully active. Click on the buttons to check that they work.
Congratulations, you’ve just built your first HUD!