If you successfully followed the previous lessons, you will now be rewarded. Here is
the ultimate in Indyjava coding. This will make your script stand from the crowd.
a) Inheritance
Now a classic programming feature, single inheritance is also available within the
Indyjava scripting language. It allows for lean, clever scripting.
Rooms and Items can derive from other ones, to inherit attributes (like images and sounds)
functions (like Command and Event blocks) and variables. The keyword that does the magic
is EXTENDS.
ROOM some_definitions { ITEM Male_Actor { INK { 5 } // Men's speech will be cyan :) } } ROOM main { ITEM Indy EXTENDS Male_Actor // Inherit all properties from Male_Actor { ANIMATION { // --- standby images --- 0 0 "gr/indy_d.gif" 1 // Looking South 0 1 "gr/indy_r.gif" 1 // Looking West 0 2 "gr/indy_u.gif" 1 // Looking North 0 3 "gr/indy_l.gif" 1 // Looking East // --- walking animation --- 1 0 "gr/indy_wd.gif" 4 // Looking South 1 1 "gr/indy_wr.gif" 6 // Looking West 1 2 "gr/indy_wu.gif" 4 // Looking North 1 3 "gr/indy_wl.gif" 6 // Looking East // --- talking animation --- 2 0 "gr/indy_td.gif" 7 // Looking South 2 1 "gr/indy_tr.gif" 4 // Looking West 2 2 "gr/indy_tu.gif" 1 // Looking North 2 3 "gr/indy_tl.gif" 4 // Looking East } POSITION { 140 140 0 } } }
Indy will talk in Cyan because it inherits all the attributes defined by its base class
"Male_Actor".
However, derived classes can also override base class' properties by redefining attributes
and functions with the same name. For example, within the Indy item, it is still possible
to specify a different INK.
Please note that inheritance refers only to the properties of the base class, but not to
its sub-tree of items. This way, if Male_Actor includes an item in its inventory, making
Indy a derived class of Male_Actor will not make that item appear into Indy's inventory.
Purists will notice that this system makes no different between 'instance' and 'class
definition'.
The file STDLIB.ADV contains useful base classes for Rooms and Items,
that you can use by just typing IMPORT "stdlib.adv" at the
beginning of your script.
b) Variables
Integer variables are the most common use of variables in an Indyjava script. But there are other types of variables :
INTEGER | Holds a 32-bit number |
STRING | Holds a line of text |
ROOMVAR | Holds a reference to a ROOM object |
ITEMVAR | Holds a reference to an ITEM |
However, ROOMVARs and ITEMVARs are not useful in the current implementation of the Engine.
INTEGER { i } // Global integer variable ROOM main { STRING { s } // Local String text variable ITEM Indy { < ... etc ... > } COMMAND use whip { IF { EQ i 0 } { LETS s "One" } ELSE { ADDS s " and more" // Concatenate after the previous word } SAYS indy s // Show the content of the String ADD i 1 } }
Variables are local to the level they were defined (a room, or an item), or global
if they were defined out of any room.
c) Procedures
The language currently supports simple procedures, without parameter passing.
Such subroutines can be useful when repetitive tasks are needed in different parts of the
script.
PROC hello_world { SAY indy "Hello procedural world!" } ROOM main { ITEM Indy { < ... etc ... > } COMMAND use whip { CALL hello_world } }
Procedures can have local variables. Also, procedures can be defined into rooms or
items, making the procedure a "local" name.
d) System functions
Some variable names and procedure names are reserved for system use. The most useful
ones are OnEntry and OnExit.
A room's OnEntry and OnExit procedures (if defined) are automatically called by the system
when appropiate.
ROOM main { ITEM Indy { ANIMATION { // --- standby images --- 0 0 "gr/indy_d.gif" 1 // Looking South 0 1 "gr/indy_r.gif" 1 // Looking West 0 2 "gr/indy_u.gif" 1 // Looking North 0 3 "gr/indy_l.gif" 1 // Looking East // --- walking animation --- 1 0 "gr/indy_wd.gif" 4 // Looking South 1 1 "gr/indy_wr.gif" 6 // Looking West 1 2 "gr/indy_wu.gif" 4 // Looking North 1 3 "gr/indy_wl.gif" 6 // Looking East // --- talking animation --- 2 0 "gr/indy_td.gif" 7 // Looking South 2 1 "gr/indy_tr.gif" 4 // Looking West 2 2 "gr/indy_tu.gif" 1 // Looking North 2 3 "gr/indy_tl.gif" 4 // Looking East } POSITION { 140 140 0 } } PROC OnEntry { SAY indy "You just entered this room" } PROC OnExit { SAY indy "You are leaving this room" } }
The instructions in the OnEntry procedure are automatically executed whenever the view
switches to that room. The same applies to OnExit, as you would expect.
The example makes little sense by itself, but you will find that OnEntry and OnExit blocks
are heavily used in adventure scripting, because they are so convenient.
e) Color Lighting
To make character walking more realistic, 2D adventure games used to change color palettes when stepping on dark or poorly illuminated parts of a room. Here, a 24 bit color filter is used to recreate such effects, through the SETLIGHTING command.
ROOM main { ITEM Indy { ANIMATION { // --- standby images --- 0 0 "gr/indy_d.gif" 1 // Looking South 0 1 "gr/indy_r.gif" 1 // Looking West 0 2 "gr/indy_u.gif" 1 // Looking North 0 3 "gr/indy_l.gif" 1 // Looking East // --- walking animation --- 1 0 "gr/indy_wd.gif" 4 // Looking South 1 1 "gr/indy_wr.gif" 6 // Looking West 1 2 "gr/indy_wu.gif" 4 // Looking North 1 3 "gr/indy_wl.gif" 6 // Looking East // --- talking animation --- 2 0 "gr/indy_td.gif" 7 // Looking South 2 1 "gr/indy_tr.gif" 4 // Looking West 2 2 "gr/indy_tu.gif" 1 // Looking North 2 3 "gr/indy_tl.gif" 4 // Looking East } POSITION { 140 140 0 } } ITEM darkzone { IMAGE { 0 0 "gr/blank.gif" } POSITION { 50 140 0 } EVENT { IF { INSIDE darkzone indy } { SETLIGHTING indy 0 0 0 50 // 50% darker than normal } ELSE { SETLIGHTING indy 0 0 0 0 // 0% dark (back to normal) } } } }
The first 3 parameters of SETLIGHTING are the Red-Green-Blue values of the desired
"target" color (black in the example). The last parameter indicates the degree
of proximity to the target color, between 0% and 100%.
It is also possible to specify a LIGHTING block for a room or an item. This would give an
initial color lighting, that can also be redefined with SETLIGHTING during gameplay.
f) Imageless items, and scaling
Several times during the tutorial, "blank" completely transparent GIF images were used to work as invisible areas. It is possible to create items without images that still have a rectangular shape, using the SIZE keyword. This saves memory by avoiding unnecessary image files.
ROOM main { SCALE { 0 30 143 50 } ITEM Indy { ANIMATION { < ... etc ... > } POSITION { 140 140 0 } } ITEM area { SIZE { 50 30 // Force this pixel size, despite the lack of an image } POSITION { 50 140 0 } } }
Further more, such SIZE statements can be used with items which do have images. That
would override the room's scaling scheme for that item.
As you must have noticed, the default scaling scheme for rooms is to show items at 100%
size at the bottom of the screen and slowly decrease to 0% size at the top, to simulate a
3D perspective. The SCALE block for a room allows to customise these 2 parameters. In this
example, characters will appear at 30% size at the top of the screen (y=0) and 50% size at
the bottom (y=143).
g) Splitting an adventure into several parts
It is often preferable to split a script into independent parts, that are played by the user sequentially.
ROOM temple { < ... etc ... > COMMAND use gun monster { LOAD "002.GAG" // After successfully completing Part I, start Part II } }
The method is to compile every ADV and save the resulting adventure under a different
name (like "001.GAG", "002.GAG" , etc...). At the right time in the
script, use the LOAD command to start the following part. Please note that all variables
and inventory items are lost in the loading process.
h) Cutscenes
An important part of a finished product is the quality of intermediate, explanatory, non-interactive, cinematic sequences. This is achieved with a subtle use of WAIT and SLEEP instructions.
ROOM main { ITEM Indy { < ... etc ... > } ITEM big_title { IMAGE { 0 0 "title.gif" } POSITION { 150 50 0 } } COMMAND use whip { // Begin non-interactive sequence MOVE big_title 150 130 0 WAIT big_title // Wait for the title to stop moving. SLEEP 1500 // Pause 1.5 seconds. MOVE indy 210 142 0 WAIT indy // Wait for Indy to stop moving. SETORIENTATION indy 0 // Look at the user. SAY indy "I like cutscenes!" WAIT indy // Wait for his sentence to finish. // End of sequence (player will take control again automatically) } }
Cutscenes can obviously appear anywhere in the script. Any language instruction can be
used in a cutscene, including switching to another room with GOTO.
As soon as there are no more WAIT or SLEEP instructions in the program flow, control is
automatically given back to the player : the cursor changes and the interface panel
reappears (so, you don't have to deal with these details).
i) Map view
Another feature of memorable games was the interface change to a verb-less mode,
typically used in map overviews.
This is achieved with the SETMAPMODE instruction.
ROOM island_map { IMAGE { "island_overview.gif" } SCALE { 0 30 143 30 } // Large scale (small characters) ITEM house { < ... etc ... > } PROC OnEntry { PLACE indy island_map // Be sure to be here. SETMAPMODE 1 // Change to special "map" interface } PROC OnExit { SETMAPMODE 0 // Back to standard interface } EVENT { IF { COLLISION indy house } { // Player reached an interesting point in the map PLACE indy another_room GOTO another_room } } }
When in "map" mode, the names of items pop up when the mouse is over them,
but no sentence can be formed by the player. The player can just walk across the map.
j) Changing item labels, and default verbs
In the standard interface, the default verb and the label of an item are combined to form a default sentence when the mouse is over the item (like "Look Whip"). This default sentence is executed if the player presses the right mouse button over the item. The default sentence that will be proposed to the player can be altered to ease gameplay, or to complicate it.
ROOM main { IMAGE { "gr/idol.jpg" } ITEM Indy { < ... etc ... > } /* And now, a bit of foreground vegetation */ ITEM plant { IMAGE { 0 0 "gr/plant_1.gif" } POSITION { 0 182 0 } DEFAULTV { Pull } // custom default-verb } ITEM plant_2 { IMAGE { 0 0 "gr/plant_2.gif" } POSITION { 256 182 38 } LABEL { "strange thing" } // Eye-catching custom label } ITEM plant_3 { IMAGE { 0 0 "gr/plant_3.gif" } POSITION { 190 182 0 } LABEL { "" } // Non-selectable scenary item } COMMAND look plant_2 // "look at strange thing" :-) { SAY indy "Nothing strange here" SETLABEL plant_2 "plant" // Use the same appearent label as the first item } }
If no LABEL is specified for an item, its name will be used as a label.
Notice how item labels can be altered at any time with SETLABEL.
Also, items with an empty label (like Plant_3 above) will become unselectable for the
player. This is useful for scenary items like vegetation.
The default verb for all items is Look but this can be specified on a per-item
basis in the DEFAULTV block. Typically, the default verb for human characters should be
set to Talk to make gameplay intuitive. To change this default verb at runtime,
use the SETDEFAULTV instruction.
k) Using the Console
After importing a script, the Immediate Mode of the interpreter is available,
from the menu Options/Show_Console.
Any language instructions (not block headers) can be tried here, like MOVE, SETIMAGE,
etc... This can be very useful to debug scenes and try commands that can be part of your
script later on.
l) Alpha channel effects
Every item has a transparency level. It is specified as a number between 0 and 100 (default is 100, meaning 'no transparency effect'). You can specify this by setting the ALPHA property of items, in a very similar way to color lighting.
ROOM main { ITEM Indy { < ... etc ... > } COMMAND drink potion { IF { EQ f 0 } { SETALPHA indy 50 // 50% transparent ! LET f 1 } ELSE { SETALPHA indy 100 // 100% opacity (back to normal) LET f 0 } } }
It is also possible to specify an ALPHA block for an item, to give an initial
transparency level.
m) Full motion video
Video clips are easily played with a single instruction WAITVIDEO.
ROOM main { ITEM Indy { < ... etc ... > } COMMAND use whip { WAITVIDEO "RAIDERS.MPG" } }
This requires Java Media Framework
extensions to be installed. These are not yet part of Java 1.2 and have to be downloaded
separately.
If these extensions are not installed or playback is not possible for whatever reason, the
Engine will silently ignore the WAITVIDEO instruction. No error message will be issued.
n) Speech
Characters can really talk if needed, but the voices will have to be recorded first.
ROOM main { ITEM Indy { < ... etc ... > } COMMAND use whip { WAITSPEAK indy "Nothing strange here" "snd/nothing.wav" } }
The first parameter is the text line that will appear, and the second one is the
filename of the recorded voice.
Once again, this requires Java Media
Framework extensions to be installed. If they are not present, the command will behave
exactly like WAITSAY.
o) Custom transitions
Several standard graphic transition effects are available when going from one room to another, and are called by their number, with the TRANSITION command. However, it is also possible to create custom transitions, written and compiled in the Java language. Such plug-in transitions are simply called by their filename, with the GOTOTRANSITION command.
ROOM main { ITEM Indy { < ... etc ... > } COMMAND use whip { GOTOTRANSITION street "custom_transition_example" } }
The code above supposes that a pre-compiled Java file named "custom_transition_example.class" is available in RESOURCE.JAR (see below).
These custom extensions have to follow some rules. The programming of such extensions goes far beyond the scope of this manual, but if you feel you have the Java skills and want to have special
transition effects in your game, check these sources.
p) Packing it up
A perfectly finished adventure should be packed, so that players can't look at the graphics before going further in the game. The quick way of achieving this is to use the JAR file format. It's similar to a ZIP file, except that it's Java-specific. The JAR utility can be downloaded from here.
The IndyJava engine always looks for a file called "resource.jar" and will use files stored there as well as those available in the current subdirectory.
To pack your stuff, issue a command like this at your Operating System prompt :
JAR -cf resource.jar gr/*.* snd/*.* *.gag
Be sure to be located in the engine's directory when doing this (type CD indyjava) .
Also, be very careful with uppercase/lowercase in all filenames referenced by your script. Retrieving files from a JAR file is a case-sensitive operation.
Notice that the demo adventure does not use this feature, for educational reasons.
Unfortunately, for maximum compatibility with all platforms (with browsers, mainly) you should avoid putting your *.GAG files and your audio files in the pack.