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 property State of object Bulb1 is set to "BulbOn"

- if this is not true, the program determines whether variable x has a value of 2

- if this is true, then the property State of object Bulb2 is set to "BulbOn"

- 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

Loop

 

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 Loop command defines the end of the code inside the loop, and returns the program to the start of the loop.


The same can be achieved with this:

 

Code:

Do Until x = 50

    'do something here

    x = x + 1

Loop

 

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 Loop command:

 

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!