indy_logo.gif (3362 bytes)

Step 9: advanced issues

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.

tutorial_9a.gif (2923 bytes)

 

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.


Next >>

Back to the main page