Important! Please read the announcement at http://myst.dustbird.net/uru-account.htm
Also! Please read the retirement announcement at http://myst.dustbird.net/uru-retirement.htm

Dustin's Seventh Tutorial

From UamWiki

I think these might not work perfectly with the newer Pyprp versions, but they're here for posterity :P

Introduction

This tutorial talks about how I made The Second Age of Dustin. It will allow you to do some simple animation, get effective buttons, journals, and Linking Books. It will also show you how to make custom fonts, and use SDL to save variables.

The source files can be found at http://www.agebuilder.org/~dustin/ageSources/Dustin2.zip.

You will want to have Dustin2.py on hand, while reading this tutorial. All the API commands are documented here: UruAgeManager API. There is a lot to be learned by studying Dustin2.py!

I assume that you are familiar with Blender, and also with programming(in some language), as well as having some knowledge of Python.

Making the Age

Making the landscape

I made the landscape, as described in Dustin's Second Tutorial. I like vertex painting!

Making the books

I made a cube and streched it out into a book shape. I scanned in the covers and spines of the Encyclopaedia Britannica, and a Sherlock Holmes book! I then put all the images for each book onto a single image(512x512). I then UV-mapped the images to the book "boxes".

In order to be able to find the position of the books, I made them physical objects, but disabled physics for them. To do this, I set the following properties in Blender.

float: mass=1.0
int: col_type=4
string: name=Dustin1Book
string: col_flags4=00000800

The "name" is what Uru will call this object. The "col_flags4" is what caused physics to be disabled for this object.(It is possible to enable and disable physics at runtime.)

The journal has

float: mass=1.0
int: col_type=4
string: name=Dustin2Journal
string: col_flags4=00000800

Detecting when the user clicks

First you need to "turn on" the receiving of keypresses.

   def OnServerInitComplete(self):
       PtGetControlEvents(true,self.key)

Take a look at Dustin2.py, to see how I do these things.

Once that has been done, you'll receive events in:

   def OnControlKeyEvent(self, controlKey, activeFlag):

Notice how I make sure that activeFlag is false. That ensures that it only does stuff, when the key goes back up(or the mouse button goes up).

Then I check if controlKey equals the key I'm looking for. A complete list can be found in PlasmaControlKeys.py. I check if it equals 1, since that is the left-mouse button(by default).

Then I check if the avatar is close to the book or button or whatever, and if so, open it.

Telling if an object is close to the avatar

You need to have a ptSceneobject, telling where the object is.
If you have a physical object, you can get a ptSceneObject, by using

Dustin2Journal = PtFindSceneobject("Dustin2Journal", "Dustin2")

You'll probably want to make "Dustin2Journal" a global variable, and call this function in OnServerInit.
I do this for each object, in the OnServerInit method.

This is how I see if the Journal is within 6.3 feet of the avatar's feet.

if(uam.isObjectInRange(Dustin2Journal,6.3)):
    uam.output('in range of journal.')
    #make book appear.

Making a Book appear

For the journal, I used:

uam.showBook('<cover src="noMipMapbrowncover.jp" ><ont size=30 face=Dustin >15.2.82...',self.key)

For the Linking Book, I used:

uam.useAgeBook('Dustin',self.key,linkingImage='noCompressDustina.png', cover='noMipMapgreencover.jp')

but you can get away with the simpler:

uam.useAgeBook('Dustin',self.key)

Setting a Variable

I only used one SDL variable: 'isPowerOn'. You have to initialise it, so that you will be notified if it is changed.(especially important in multiplayer, where someone else might change it.)

uam.initSDL('isPowerOn',self.key)

When the button is pressed, I change it's value by using:

powerOn = uam.getSDL('isPowerOn')
uam.setSDL('isPowerOn',not powerOn)

I don't do any animation at this point. I do it when I get the OnSDLNotify message:

def OnSDLNotify(self, VARname, SDLname, playerID, tag):

that way, the animation gets done, no matter which player(in multiplayer) pushed the button.

Note that you should read the SDL variable, when the Age is loaded(in OnServerInit), so that everything is in the right place!
I do this at the end of OnServerInit.

Simple Animation

For the power switch animation, I use the routine setPower, to set things up for the animation and begin it. Then I use OnTimer to do each part of the animation. For the sphere animations, I just use the OnTimer routine.

To move a physical object to a position (x1,y1,z1), you can:

p1 = ptPoint3(x1, y1, z1)
s1.physics.warp(p1)

where s1 is the ptSceneobject, as described above.

The animations I do, are sort of mathy, and you're welcome to figure each part out if you want, but there is no need for your animations to be of this type.

To make the animations realtime, instead of frame-based, I find the start time using time.clock().(You'll need "import time" at the top of your file.) Then I find the time difference to find which part of the animation I should draw.

To ensure that everyone sees the spheres in the same position, I base the starting of the time on PtGetServerTime().

Making a font

Files I used: Dustin2.bmp and Dustin2c.dat

I drew my font on white paper with a Jiffy marker! The capital letters were about 2 inches tall, in my case.

Then I scanned them in and shrunk their size, so that they would be about 30 pixels tall. Then I made it greyscale(you don't need to do this, it will just read from the blue pixel), and put them all on the same .bmp image.

It turns out that Uru squishes the fonts horizontally, to half the size. So, I resized the image by halving the height, but keeping the same width. Then I saved it(as Dustin2.bmp).

Then I opened the .bmp file in UruAgeManager, using the Font Tool, under the "Advanced" tab.

For each letter I selected the area of the image that I wanted. Keep in mind, that if you put a box just around the picture of, say, quotation marks, without any whitespace below it, then it will wind up on the bottom of the line!

My image was black text on white, so I selected that. I named the font Dustin, and set the size to 30(which it isn't(because I changed it), but you have to use this when you use it in the Plasma Engine HTML).

I saved the font as Dustin2c.dat. Then I chose "construct font" and it output Dustin.p2f in the /dat folder.

Then you can use the font in the Plasma Engine HTML.

Textures

Where to put them

I just put mine on "plane" meshes, where no one would see them.

noCompress and noMipMap

In order to make the PrpBlenderPlugin export my textures, as not MipMaps, or as not compressed, I used the following modification on the Nov.11/2005 PrpBlenderPlugin: http://www.agebuilder.org/~dustin/uploads/alcbasicprptypes.py.

If the filename begins with noMipMap, then it won't be saved as a MipMap.
If the filename begins with noCompress, then it won't be saved as compressed or a MipMap.

Transparency

I made the linking Image "noCompressDustina.png" with translucency. As far as I know, you have to use a .png file.

It must use the above hack, with "noCompress" at the beginning, or else it won't be visible in the book!

Book covers

These can't be MipMaps, either, so my two book covers: noMipMapGreenCover.jpg and noMipMapBrownCover.jpg begin with noMipMap.

These two names were so long, that the last letter was cut off, when the name was put into the .prp file. You can use PrpExplorer to view the names, if you want. That is why, in Dustin2.py, these images end in ".jp" instead of ".jpg".

Appendix: Working with Python

Here is some useful information for working with Python:

Good questions!

All you need at the beginning are the "import" lines. Then you can have any "global" variables that you want(I've got some in dustin2.py). Then you must have the: class yourAgeName(ptResponder,): You must have that entire glue section at the end.(You can just copy and paste it from Dustin2.py or almost any Cyan file. They're all identical.)

3 of the 4 showbook() have a "#" in front, which is how you tell Python to ignore a line. Those are either tests or previous versions.

To compile your Age file, you need to install Python 2.2.3, which you can download here: http://www.python.org/ftp/python/2.2.3/Python-2.2.3.exe. You must have this version installed, but you can also have Python 2.3.5 installed at the same time.


What you'll want is the following:

1) Install Python 2.2.3 to C:\Python22
2) copy C:\Python22\Lib\compileall.py to whatever folder you have your Age's Python file in.
3) make a text file named compile.bat , and put the following 2 lines in it:

C:\Python22\python compileall.py | more
pause

4) whenever you want to compile just run compile.bat!


Note that it will recompile any files that have changed since the last compile. It will also tell you if there was an error compiling any of them. Unfortunatly, due to the design of Python, it is easy to make mistakes that the compiler won't tell you about, but that you have to run Uru before you'll find them! I recommend using the uam.output command to output a lot of messages, such as uam.output('got to OnServerInit') This command will only output to UruAgeManager.out(a text file) if you turn on "API logging" in the "advanced 2" tab in UruAgeManager. These messages are the main way that you can figure out what's going wrong, how far you got, what data a variable contains, etc. You may still want to check out the Uru log files with the ELF viewer(available at http://huru.aegura.com).