MadMax's Quick Future Pinball Scripting Guide
Revision 1
3rd November 2005
The original HTML version of this guide can be found at
this URL: http://www.futurepinball.com/forum/viewtopic.php?t=434
For those of you who think they can't program scripts
for Future Pinball (or formerly Visual Pinball), this will teach you very
quickly what to look for. Scripting with FP is easy, in fact a lot easier than
VP because the objects you place on the table have so many predefined functions
and parameters that all you really need to do is insert your table rules,
scoring, and light stuff.
It is inevitable that you use FP's manual and look up the last section,
Table Components, to find out how to script the objects on your table. This guide will not cover scripting FP objects at all, but I want to set
you up so you can get started right away!
1. OOP: Object-Oriented Programming
I won't start by explaining the full concept of OOP;
for simple scripts it's generally not as relevant as for application
programming. But in the case of Future Pinball, OOP could be described as this:
OOP takes references to the objects in the code environment (in this case, the pinball table)
and lets the code interact with these objects through events which the objects can trigger themselves.
The most common event for almost all of FP's objects is
the _Hit() event. It is triggered by
an object when the ball (naturally) hits it and if the script code provides a subroutine for this event, the sub will
be called and any code within it will be executed. An example:
Sub Bumper1_Hit() 'this bumper has been hit by a ball
AddScore(100) 'add points to the
player's score
End Sub 'end of bumper hit subroutine
(For further code examples on this page, the CODE tag
will be used.)
What this subroutine does is this:
-
it's called by an object named
Bumper1 because this bumper has been hit (→ the _Hit() event is sent to the
script by the bumper in that moment, and because our subroutine is there, it
catches the event)
-
the code within the sub is executed: AddScore() is handed a value of 100, so
it awards 100 points
-
the sub is terminated by the End Sub command
In FP's case, OOP means that every object has its own
subroutine in the code and these subs catch the events an object sends to the
code. When a sub is executed, none of the other code lines are relevant; only
the sub "exists" in its own timeframe and therefore all relevant code
must be placed inside it. Of course there are ways to have general code stored
elsewhere so that many subs can access it remotely, but we will look at this
later.
The only code that FP executes outside of subroutines
is the declaration part that you see first when you open the script editor. All
of the code that comes BEFORE the first sub starts is executed when FP boots up
your table, so if you want anything to happen before a game is started, you
have to place it here. There is also a subroutine called FuturePinball_BeginPlay() which is automatically run when the
engine starts, so any commands that can't be fit into the declaration part go
here.
2. Code Notation
Before we proceed, let me summarize the rules of Visual
Basic code notation:
An apostrophe (') is the indicator of a comment line.
Any line starting with ' will be ignored by the program and not be executed.
You can place this indicator anywhere in the code and everything that comes
after it in the same line will be regarded as a comment (usually coloured
green). This is also helpful if you want to disable a command in the code
without deleting it.
Between operators (+ - * /), equality signs (=, <,
>, <>, >=, <=) and numbers, spaces are optional. You can type x=1+1, but you can also type x = 1 + 1 and it will have the same
effect. However, unless you want to separate any commands or tags from each
other, spaces are NOT allowed.
The dot (.) is used to note an object followed by its
property or command, and may not be spaced apart from them. For example, typing
DropTarget1.SolenoidOn will result in
the object named DropTarget1 firing and holding its solenoid. Typing Bulb1.State = BulbOn will set the State
property of object Bulb1 to "BulbOn".
General syntax: <objectname>.<property/command>
An object-oriented subroutine is always written in this
format:
Code: |
Sub
<objectname>_<eventname>(<parameters>) <code> End Sub |
with <parameters> being optional parameters which
can be handed to the sub to be processed by it. For example, typing (ByVal x As Integer) will hand the sub a
variable x that can take a number from outside to be processed within the sub.
More on this later.
The declaration part of a script is used to declare
variables and constants as well as executing com-mands which have a global
effect on the whole code (such as the Option
Explicit you see right at the top of a default script). Variable
declaration can also be done within a subroutine, but this usually means that
the variable is only available to this sub and none of the others while global
declaration readies a variable for use in every sub.
In an equality, the left side always receives the value
of the right side. x = y means that x
gets the value of y and not vice versa.
It's common OOP practice to intercalate your code. It
makes it easier to read and evaluate than typing each line from the beginning.
If you type this:
Code: |
If (x = 1) Then Bulb1.State = BulbOn AddPoints(1200) End If |
it will be much harder to read than this:
Code: |
If (x = 1) Then Bulb1.State = BulbOn AddPoints(1200) End If |
Using the TAB key on your keyboard (on the very left
side above Caps Lock) you can introduce a tabulator space in the line. Future
Pinball will even mark the number of tabulator spaces for you with light gray
pipes so you can easily follow your intercalated code. Insert one tabulator
space per "level", i.e. if you work in a block of things, one space
will suffice while opening a second block inside the current block would call
for a second level and thereby two spaces.
Placing brackets around equalities can be a good idea,
but it's rarely a requirement as far as I know. Whether you type If x = 1 Then or If (x = 1) Then shouldn't make a difference, but if you type a
complex calculation or a connected series of equalities (using NOT/AND/OR/XOR),
brackets are good or might even be required.
Always comment your stuff if you have the time. Not
only will it make it easier for yourself to read your own code after two days
of partying, but also think about the people who will view your script when
they examine your table! FP tables are "open source" so to speak and
clearly showing your users how you did your work will encourage them to learn
and try out their own ideas, resulting in more table authors!
3. Variables: A Quick Overview
Variables. They are a pain in the butt sometimes. When
you write applications, variables are used in gross style and often you make
little mistakes with them if you're not careful. The most important thing to
remember about a variable is its setup: what kind of input can it accept and
can it be read/ written in that subroutine? If you think you get wrong return
values from your program, double check whether your variables are correctly set
up.
A variable is represented by any combination of letters
and numbers. You can have a variable called x and another variable called
ThisIsCNN2. It doesn't matter as long as your variable is not exactly named
like a reserved expression, i.e. a command in the programming language. Your
variable cannot be named Option because this is a keyword in Visual Basic, and
therefore in Future Pinball. The better you know the language, the less likely
such double namings will become.
Every variable has a data type which defines what kind of value it can carry. The two
main kinds that differ from each other are digits and characters, or numbers
and strings in a wider sense. Numbers are numeric values and are interpreted
this way, i.e. you can calculate stuff with them. Characters or strings are
interpreted as symbols and words instead of numbers, so these cannot be
calculated with; however, strings can be added to each other to form a new
string. Usually you have to be very careful about the data type when you want
to do something with a variable. If it has a type that's not com-patible with
the task (like calculating with strings), it won't work.
Common data types of variables:
integer - a numeric value between
-2,147,483,648 and 2,147,483,647. This type can be used to calculate, i.e. if
you assign 24 to an integer variable x and then type y = x + 2 you will assign the calculated value 26 to another
integer variable y.
... x = 5
... iTest = 562879
short - a smaller version of the integer
type (with 2 instead of 4 bytes) supporting between -32768 and 32767.
... x = 11
... shTest = -15384
long - a larger version of the integer
type (with 8 instead of 4 bytes) supporting quite a range of numbers (too much
to spell it out here).
... x = -8
... lTest = 121314151617181920
string - not a numeric type, instead used
to store character strings (like words or sentences) by assigning such a string
in double quotes, e.g. x = "test
message". You can assign a number this way, but it will be treated as
characters and not a real number, meaning no calculations are possible. You
would have to convert the string with the number into a real integer value
first.
... x = "this is a string"
... sTest = "this is another string"
Boolean - used to assign a Boolean operator,
either true or false. Use such a variable to check whether something is
true/active/enabled or not.
... x = true
... bTest = false
single - single-precision floating point.
Can be used to assign large numbers with floating points (like 3.14159).
... x = 1.35
... siTest = 687.1234567
double - double-precision floating point.
Uses 8 instead of 4 bytes over single-precision, allowing for even greater
floating point numbers.
... x = 0.486
... dTest = 1234567.121314151617181920
Naming variables:
If you use short variable names, like x, y or z (or any
letter or short combination of letters), you will need to know exactly at all
times what these variables stand for and of what data type they are. But if you
want to be on the safe side from the start, try giving your variables longer
names, like words, which start with a small letter like i for the integer data
type or s for string type variables, i.e. sPlayerName or even iOption. This
way, you will recognize the task of a variable based on its name, you don't get
any code keywords mixed up with your variable names (because of the prefix),
plus this notation (which I first encountered in UnrealScript) will help you
recognize of what data type your variable is (iTest is easier to recognize as
being integer than only Test or x).
For example purposes, I will mainly use short-named
variables in this guide because most of them are integer variables, and will
only be used for quick and temporary calculations. This is another conven-ion
you can pursue, writing integer/numeric variables as short letters (especially
if they exist only in a single subroutine and won't be used in other parts of
the code) while sticking with longer names and data type prefix letters for
non-numeric or script-global variables. Of course, if you have a variable x
that you use globally in your whole script, it might be difficult for you to
remember what this variable stands for, e.g. if it carries the number of
players, you might rather want to name it iNumberOfPlayers or something
similar. If you only need a variable for a quick calculation in a single
subroutine, you can simply declare x, make your calculation, and exit the sub,
because x will be lost afterwards. Try to optimize your variable naming
convention so that you can work fast but effectively.
Declaring variables:
Declaring a variable is easy: simply type Dim <variable> in the declaration
part to declare a variable for global script-wide use, or inside a subroutine
to use this variable locally, only as long as the sub runs. Remember that local
variables will be lost and never seen again once the sub is terminated, so if
you want to keep a value that you assigned to a local variable in a subroutine,
you may want to assign this value to a global variable.
When writing applications in true Visual Basic, it's
common to declare a variable's data type along with it, like this:
Code: |
Dim x 'no data type
defined Dim y As Integer
'integer variable Dim lTest As Long 'long
variable Dim sTest As String
'string variable |
Actually this is not necessary because if you don't
define a data type when declaring a variable (as visible in the example code's
first line), you effectively declare it as variant,
and automatic data type. This means that the program will use the first
assignment of a value to the variable to specify the data type itself, i.e. if
you declare a variable as variant and then assign a string to it, the variable
will auto-matically receive the string type. However, in the reality of Visual
Basic and application programming, if you don't want to mess up your code and
give clear guidelines on how your script is operating, it's always the safest
possible idea to explicitly declare the data type along with the variable. In
fact, many programming languages require you to do it or else they will return
an error, but Visual Basic is an exception since it tries to be as easy and
user-friendly as possible. Still, it can cause unpredicted results and should
be avoided!
Unfortunately for that matter, Future Pinball uses
Visual Basic Script, a lighter version of the language that does not accept any
explicit data type variable declarations. If you try to declare a variable with
a data type like discussed above, FP will return an error. VBS therefore
operates on the variant data type with every variable that you use, and you can
only declare it without specifying a type. Since FP only offers us VBS, we have
to live with the variant data type which means, be double careful with your
variables as you might never know for certain of what data type they are unless
you know exactly in what line they receive their value and thus their data type
assignment. Yet another reason to stick with the prefix letter convention to
make identifying data types easier (unless you prefer to use short names like
x, y, z).
Our forum user eXentric had this to say about the
assignment of data types to variant variables through values:
You could say that [a variable's] type becomes defined [by assigning a
value to it] because you cannot do invalid things like adding a string with a
number. But even after assignment you can't think of the variable's type as
being set in stone because you can always reassign the variable.
Code: |
Dim A A = "Hello
World" ' It's a string now for a while... A = 5 ' It's now a
number and there was no error generated |
That kind of thing is hard to keep track of. Basically, once you've
declared a variable and used it for a purpose, stick with that purpose or
define another variable. If you reuse the same variable for various purposes,
you'll really cause yourself a headache when trying to debug.
Variable operations:
Assigning the value of a variable to another: simply type the variable you want to receive the value and put a = in,
followed by the variable to give the value. x
= y will assign the value of y to x, overwriting x's original value.
Calculating or combining variables is easily possible with operators (+ - * /),
like x = y + z or within the same
variable (see below).
Calculations within the same variable: if you
want to take the value of a numeric variable, recalculate it and assign the new
value to the same variable, all you need to do is x = <add calculation here> x <add calculation here>.
For example, typing x = x + 1 will
add 1 to the value in x, and give x the new value, thereby raising the value of
x by 1. It's not necessary to use a second variable for this.
Combining strings in one variable: you can add a
string to an existing string in a variable, much like you would add two
typed-out strings this way: "string1"
+ "string2" which would result in "string1string2". Simply
do it with a string variable (or two or three or as many as you like) and add
the typed-out string to it with a +, like this: sTest = sTest + sAnotherTest + "string3".
Converting data types: you can convert strings to
numbers or Boolean values, and vice versa. For a detailed (but quite short)
explanation, please see the following URL: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcn7/html/vaconstringconversions.asp
4. Conditional Sentences
To make a program do what you want it to do, some basic
structures have to be implemented into your code. Like spoken language,
sentences in programming languages require grammar to work. We will start with
the most casual of them, the conditional sentences. Depending on your
knowledge, you may find this familiar:
Code: |
If (x = 1) Then Bulb1.State = BulbOn ElseIf (x = 2) Then Bulb2.State = BulbOn Else Bulb3.State = BulbOn End If |
What this block of commands does is this:
- the program determines whether variable x has a value
of 1
- if this is true, then the
- if this is not true, the program determines whether
variable x has a value of 2
- if this is true, then the
- if this is not true either, nothing else is checked;
instead the program proceeds to turn on Bulb3
What is happening here? A conditional sentence like
this uses the If <condition> Then
part to check whether that condition is true. What kind of condition you are
asking for is up to you: you can check a variable for a specific value, you can
check an object on your table for a state or other readable pro-perty, or you
can check anything you can read from the code. What matters is that if the
condition is true, the code after Then
is executed until it reaches another conditional keyword, i.e. ElseIf, Else or End If. These
keywords are there to check for other conditions (ElseIf) or to ignore all conditions (Else) and proceed with the code after them. Once the program has
reached End If, the conditional
sentence is terminated and the code moves on after it.
Notes:
You don't have to use ElseIf and/or Else at
all. If you just want to check for a single condition with a single outcome,
one If <condition> Then
<code> End If will suffice.
If you don't want to write a whole block of code, for
example because your condition and the outcome are very short, you can also
type the whole sentence in one line, without End If. If you want to include more than one line of outcome code,
but still fit it in a single line with the sentence, you can use a colon ( : )
between two "lines" to make one line out of them: If <condition> Then <line1> :
<line2> : <line3>.
To check for more than one condition, you will need to
use NOT/AND/OR/XOR (see below).
Since the word "else" implies that only this
OR that can be executed, it's not possible to execute two code blocks because
two conditions in the sentence are true. The program will simply execute the
code of the first true condition and then terminate the sentence. To execute
more than one block of code for more than one true condition, you would have to
use two If...Then...End If sentences
after each other, thereby dedicating one sentence to one condition.
Boolean operators: NOT/AND/OR/XOR
If you check for conditions, sometimes you will want to
determine if more than one condition is true. To do this, there are four basic
Boolean operators available to you:
NOT - checks whether any condition is not true. You can
use this with only one condition to invert the result, i.e. if the condition
comes out to be true, then it is false (because it's NOT true) and the program
moves on. If you use it on more than one condition, you can check whether one
condition is true, but not the other.
If NOT (x = 1) Then
If (x = 2) NOT (y = 3) Then
AND - checks whether two or more conditions linked with
this operator are all true. If one of the linked conditions turns out to be
false, the program moves on.
If (x = 2) AND (y = 3) Then
OR - checks whether one or more of two or more conditions
linked with this operator is/are true. Note that this also includes the AND
case (all conditions are true). You could translate it as "if any of them
is/are true, no matter whether it's one, two or all".
If (x = 2) OR (y = 3) Then
XOR - checks for a "true" OR between conditions
linked with this operator. This means that if all conditions are true
("any of them is/are true"), this returns false, not true. For XOR to
return true, only one of the two conditions linked with it may be true and the
other must be false.
If (x = 2) XOR (y = 3) Then
5. Case Sentences
Case sentences are much like conditional sentences, but
they can be easier in notation. Note though that case sentences are limited to
checking a single condition (often a variable) for n results. For example, you
can have a numeric variable and check it for several values, then execute code
if one of these values is met.
An example:
Code: |
Select Case x Case 0 'do something here Case 1 'do something else here Case 2 'do something totally else here Case Else 'do something completely else here End Select |
In this example variable x is checked for values 0, 1
and 2. If one of these values is met, the code after the Case keyword is executed, then the sentence terminates. If none of
these values is met, the code after Case
Else is executed.
6. Loops
Sometimes you'll want to repeat stuff in your code, for
example when you go through a light sequence or if you want to repeat a
mathematical operation numerous times until you get the final result. There are
two ways to do this.
The For...Next loop:
A loop which has a starting point and a fixed end point
at which it terminates. The runs through the loop are counted and once the end
point value is reached, the loop stops.
Code: |
For i = 1 To 50 x = x + 1 Next i |
This creates a loop which runs from starting point 1 to
end point 50, thus 50 times, and each time increases the value of variable x by
1. So, if this variable was 23 when the loop started, it will be 73 at the end.
Notice that i is also a numeric variable which is
counted from 1 to 50 here. The Next i
command advances the value of the variable by one and restarts the loop unless
the value has reached 50.
The Do...While and Do...Until loops:
Code: |
Do While x < 50 'do something here x = x + 1 |
This example uses the Do keyword to start a loop that will last WHILE the value of x is
smaller than 50. This means that the loop will stop BEFORE commencing another
time once x = 50 is reached. The
The same can be achieved with this:
Code: |
Do Until x = 50 'do something here x = x + 1 |
It does exactly the same as the above example, however
this time variable x is not checked to be below 50, but equal to 50. The
keyword Until specifies that the loop
will recommence UNTIL x reaches exactly 50. Writing While x = 50 here wouldn't even have started the loop unless x
would have been 50 from the start. So, where While is used to check for the duration of a condition, Until checks for a single point in time
when that condition is met.
It's possible to let the loop commence another time and
THEN stop if the condition turns out to be true. This is simply achieved by
putting the While/Until keywords
behind the
Code: |
Do 'do something here x = x + 1 Loop While x < 50 (Loop Until x = 50) |
Naturally you won't want to simply count the loop runs
with a variable and make it stop when that variable reaches a value. That's
what the For...Next loop is for! But
if you happen to have a condition that changes as the loop repeats, you can
check this condition for a state and stop the loop at that point, either before
it recommences or after it has.
One more thing: Do
loops completely reserve processor time for themselves, meaning that no other
code in your script can be executed as long as the loop is running. If you
accidentally get caught in an infinite loop (because the condition to stop it
is never met), you won't be able to continue the code at any point (an
application with such a loop would simply freeze). If you implement the keyword
DoEvents inside your loop, the loop
will stop every time it reaches this point and check all other
events/subroutines for code to be executed. This way the game can move on while
the loop is still running, as the rest of the code won't have to wait for the
loop to finish, and if the loop gets caught in infinity (which is a bug and
must be solved by you!), your script/program at least won't freeze.
7. Sharing Code Throughout Your Script: Self-Made Subroutines
This is the essence of good code writing. Say you've
got three subroutines executed by events triggered by playfield objects. And
all these subroutines are supposed to do the same thing. Like this:
Code: |
Sub Target1_Hit() Flasher1.FlashForMs 300, 75, BulbOff Flasher2.FlashForMs 300, 75, BulbOff PlaySound("sfx_TargetMade") AddPoints(500) End Sub Sub Target2_Hit() Flasher1.FlashForMs 300, 75, BulbOff Flasher2.FlashForMs 300, 75, BulbOff PlaySound("sfx_TargetMade") AddPoints(500) End Sub Sub Target3_Hit() Flasher1.FlashForMs 300, 75, BulbOff Flasher2.FlashForMs 300, 75, BulbOff PlaySound("sfx_TargetMade") AddPoints(500) End Sub |
That's tedious. You have to write or copy and paste the
same code into all three subs. Not only is this more work (especially when it's
about more than those four lines), but it's also harder to read when you have
to scroll through masses of repeating code while overseeing your script, and it
clutters up the size of your resulting table file! A good programmer tries to
keep the code as small as possible so that it will be executed as fast and be
as small a file as possible. The solution to the problem displayed above is,
write the code once and then link the three subs to this code so they can use
it remotely!
Code: |
Sub MySub() Flasher1.FlashForMs 300, 75, BulbOff Flasher2.FlashForMs 300, 75, BulbOff PlaySound("sfx_TargetMade") AddPoints(500) End Sub |
We have just written our own subroutine called MySub()
and put it somewhere in the code. Preferrably you could make your own section
somewhere near the end of the script exclusively for those self-made subs. But
this sub cannot live on its own, and since it's not connected to an event, it
cannot be triggered by an object on the playfield. Solution:
Code: |
Sub Target1_Hit() MySub() End Sub Sub Target2_Hit() MySub() End Sub Sub Target3_Hit() MySub() End Sub |
And that's it! We saved three lines per subroutine and
wrote four lines into our own sub elsewhere in the script. Now if any of the
three subs is called by the _Hit()
event, it will simply execute MySub() from there. Next, the code in MySub() is
executed just like it would have been done had we left it in the event sub.
This is also the perfect point to introduce arguments. If you have a subroutine you
wrote yourself that calculates something (like a function) and you want to call
this sub and tell it to calculate differ-ent values at different times, all you
have to do is hand the value over to your sub and let it take this value as an
argument, a parameter in between the two brackets () after the sub's name. Your
sub then takes this argument, stores it in a variable you put between the
brackets, and calculates using the variable - and thereby the value you handed
over to it.
Code: |
Sub Anything_Hit() CalculateThis(19) End Sub Sub CalculateThis(ByVal
x As Integer) iResult = x * 2 + 6 'do something with the result End Sub |
Something triggers the sub using the _Hit() event. Next, this sub calls our
self-made sub CalculateThis() and hands the value 19 over to it. Because
CalculateThis() has an integer variable x inside the brackets, it can take the
value 19 as an argument into the parameter x, then use it in the calculation.
If you call the sub again with another value, it will just as well use this
value (inside x) and naturally return a different result. The result of the
calculation is saved in the integer variable iResult. After this, you can do
whatever you want with this variable.
Note the following fact: x is only argumentative which
means it only exists inside our self-made subroutine, and its declaration is
inside the brackets (ByVal x As Integer).
ByVal means that this variable stands
on its own and the value (19 in this case) is copied into the sub, and if x is
changed in any way, it will not affect any x outside of the sub.
If you have a variable x in the original sub which
calls CalculateThis(), and you use this variable x as an argument when calling
CalculateThis(x), this does not mean that it's the same variable. As a matter
of fact, x in one sub is never the same as x in another sub, and if x = 19 and
you call CalculateThis(x), this means that the value 19 inside x is handed to
CalculateThis(), that's all. It's the same as calling CalculateThis(19). Don't
get confused by that.
Also, if you want to use iResult outside of
CalculateThis() so that other subroutines can access the result of your
separate calculation, you have to declare iResult as a global variable in the
beginning of the script.
8. What's More?
As far as I can remember from my years of Visual Basic
(among other languages) programming experience, this should be all of the
basics you need to know to understand how a Visual Basic program works. At least
it should help you a great deal when you go over the script of a Future Pinball
table again and look for clues on how things were done. Remember: a program is
structured and tailored, and subroutines are what counts in object-oriented
programming. Only what is needed will be executed by the program, and once you
can detect which subroutine is for which event of which playfield object (which
you should be able to do by now), you can also look in the right spot and find
answers to your questions. Conditional sentences, case sentences and loops are
the most common stuff you'll find in OOP (and in many other languages too), and
you have also learned how to deal with variables. If you're any average in
math, you have noticed how easy it is to create a function using a self-written
subroutine and have this function calculate the value in your variable, just
like you'd write it down on paper. Such calculations might not be so important
in pinball scripting, but if you want to create something cool, you'll need it.
And by the way, there are real functions in Visual Basic too (there is a
Function keyword in the language), but I don't know in how far it would be
compatible with FP, let alone necessary. If you want to learn more about the
language or just increase your vocabulary of terms and commands, be sure to
swing by the MSDN's Visual Basic section at this URL: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon98/html/vbconProgrammersGuide.asp
Also, be sure to look at the FP manual to read the
scripting basics again and to inform yourself about the events and states you
can work with for the playfield objects.
Good luck with scripting, and make some kick-ass tables for FP!