Version 2.0!
Features
Tutorials
Files
Glossary
Projects
Contact
Links
Message Board
Extras
LuckyCam
Old News
Sign Guestbook
View Guestbook
VB Horoscope
VB Photo Album
.
ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!

Using the ScriptControl Object
By Q

Welcome to my first tutorial! From the outset, I should say that this - as far as tutorials go, is pretty far removed in a practical sense to gaming. However, the concepts can be used very easily to enhance a game. I'm assuming that you already have a good concept of Visual Basic, especially the class object. And although a knowledge of VBscript is not required to understand this tutorial, it will help. In reality, the ScriptControl object can be used to used to run VBScript, JScript (Microsoft's almost-exact replica of JavaScript) or a number of other scripts. Having most of my experience in VB and C++ rather than Java, I'm going to stick to VBscript for my scripting examples.

The last thing to mention before we begin is this - the ScriptObject, even when used properly, has some nasty overhead associated with it, mainly when frequently being called on to run large scripts. However, this varies from instance to instance, and how effeciently it's capabilities are being handled. So if you're writing a first person shooter or action game using full direct3d functions and every little nanosecond of CPU time is important - perhaps this isn't for you. But if your game is turn based, or uses simple sprites, it may just work beautifully and make your life easier!

So, without any further ado, let's begin! First things first, we need to add the Microsoft Script Control 1.0 from the components list. If you don't have it (if you're using earlier versions of VB, this may be the case) you can grab it from the Microsoft web site.



Once this is done you'll have access to the ScriptControl object. Novel idea, huh?



For the duration of this tutorial, I'm going to assume your script object has been named scrMain. Now, let's look at the default settings for the script object.


The important properties to us at the moment are...
AllowUI - If set to True, allows the scripter access to the user interface based functions, such as "MsgBox". Needless to say, this should be disabled in a full-screen direct3d/directdraw based application, unless you're a sadist.
Language - Pretty obvious. Set this to JScript for Javascript, or whatever else you want to use. Can be changed on the fly, but it's a bad idea to do this in case code has already been registered and validated by the ScriptControl object.
UseSafeSubset - Turn to "True" to make the script less capable. Stops nasty hackers from coding a worm or something equally as nasty in your in-game scripting language! Actually, a competent coder could probably still do it, so be careful. If you let the world execute scripts in your program, you are taking a risk.
Timeout - Here is a good way to in some small way make vicious script problems less vicious. This gives a script a time limit (in milliseconds) for it's execution, before being cancelled forcefully by your program.

So, for example, to change our coding method to JScript, we'd do something like this...


scrMain.Language = "JScript"


Now in an extremely basic sense, we can execute code in our scriptcontrol by typing the following...


Dim Code as String
Code = "MsgBox ""This is a messagebox called by my vbscript function"",vbinformation"
scrMain.ExecuteStatement Code


This, obviously, is limited in it's usefulness. Nonetheless, it can be quite functional. For example, in my game, I have the ability (when in debug mode) to send single lines of raw vbscript/javascript, giving me enormous abilities as far as debugging goes.

With the UI disabled, and safe mode on, we now have a functional scripting language, but with no functions above the basic ability to dim variables and perform math functions. This is where we begin the fun bit - we can extend the scripting language for use with our game. We do this by creating classes in our program, and share them with our scriptcontrol object. This lets our scripter call and use anything from a function or sub to a variable from our class object. And this class object is actually physically part of our program - giving -it- access to our game code. This buffer system stops scripters from being able to access lower level stuff in our game that might otherwise be dangerous to give them access to. Imagine if we gave our scripters the ability to cache new textures and put new objects on the screen? Of course, nothing stops us from doing this. Maybe you -want- your scripters to do this. Or maybe you just want them to change the state of a single object of your choosing. It's up to you. It's your scripting language.

So, let's give the user in our program a sub to allow him to print text to an output textbox we've created for him. Inside a class object we've named clsPublicFuncs let's slap the following code...

'inside clsPublicFuncs

Sub PrintOut(ByVal str As String)
    frmMain.txtOutput.Text = frmMain.txtOutput.Text & vbrlf & str
End Sub

Having done this, we need to allow the scripter access to these functions.

'inside frmMain

Private Sub FormLoad()
    Dim SharedThings as clsPublicFuncs
    scrMain.AddObject "Cls", SharedThings
End Sub

By adding an object, "cls", which is in fact the object "SharedThings" in our program, we've exposed or shared access to those functions to the scripter. So, in our script, we now have a command called "PrintOut" which takes a single variable, a string, and performs a task with it. In this case, printing it to the screen. TO acccess it using vbscript, we'd type the following in our script...

'inside our script

Cls.PrintOut "Hello, World!"

An important note to make is that in most scripting languages, most notably VBscript and JScript, variables do not have types like in VB - they are all variants. It is important to keep this in mind when developing extensions to your language. You will notice that in the sub "PrintOut", we are accepting our string by value, rather than taking a 'real' string. The reason is simple - given that all variables in our scripting language are variants, it is
not possible
to pass a 'real' string. However, using ByVal is just one way of dealing with this problem. Another solution would be to just accept a variant straight out in your subroutine. This, however, brings up a whole different issue of security and program stability that I won't go into here.

As useful as this is, there's something else we can do to make our scripters lives just that much easier. Instead of exposing our class object to the script like we did before we can do it this way...

'inside frmMain

Private Sub FormLoad()
    Dim SharedThings as clsPublicFuncs
    scrMain.AddObject "Cls", SharedThings, True
End Sub

See that "True"? It's an optional third parameter to the AddObject sub. If set to True, it adds that object's functions and whatnot to the actual scripting language itself, rather than just exposing the object. This means that now our scripter could use the following code...

'inside our script

PrintOut "Hello, World!"

So, now you know how to extend the scripting language. But wait, there's more! As we said before, it's pretty basic being able to add lines of code one by one, and very slow too. The lines of code are executed one by one - nice for console-based line entry in your game, but not good for actual scripts. So we get to another ability of the script control. Take this, for example...


Dim Code as String
Code = "Sub Main" & vbCrLf & "PrintOut ""Hello, World!""" & vbCrLf & "End Sub" & vbCrLf
scrMain.AddCode Code
scrMain.Run "Main"


In this example, we're sending three lines, and a defined function, to the scriptcontrol object. Then we're calling the Run command, and telling it to run the procedure "Main". This means that we could, for example, load a script from a file containing multiple functions, subs, etc, and pass them to the scriptcontrol object, to be run at a later stage in our program. "Think" routines for an AI bot, perhaps?

However, something we've been missing up to this point is error handling. What if there's a script error? Hey, it happens, we're only human! So, to catch errors there's a number of functions. The first is fairly simple. After using the "AddCode" capability, we can use the standard error catching system, for example...

On Error Resume Next
Dim Code as String
Code = "Sub Main" & vbCrLf & "PrintOut ""Hello, World!""" & vbCrLf & "End Sub" & vbCrLf
scrMain.AddCode Code
If Err Then
    With scrMain.Error
        txtOutput.Text = "Script Syntax Error : " & .Number _
	& ": " & .Description & " at line " & .Line _
	& " column " & .Column & ": " & vbCrLf & txtOutput.Text
    End With
Else
    txtOutput.Text = "No Load Errors." & vbCrLf & txtOutput2.Text
End If
scrMain.Run "Main"


So, now we can trap syntax errors at load-time. What about runtime? Well, we use the same system, right after the scrMain.Run command.

If Err Then
    With scrMain.Error
        txtOutput.Text = "Script Runtime Error : " & .Number _
	& ": " & .Description & " at line " _
	& .Line & vbCrLf & txtOutput.Text
    End With
End If

Leaving us with a reasonable system for trapping errors during script loading or execution. Maybe you want to crash out on -any- script error, or maybe you want to continue on nonetheless. It's your choice.

This brings us to the final fairly important concept with ScriptControl objects. Modules. These allow us to run various 'virtual' machines inside our ScriptControl object. Make sense? No? Let me explain. Going back to our line-by-line entry method before, we could have done the following...

'inside our script

Dim X
X = 1
PrintOut X

...and received a "1" for our troubles. If another scripter tries to declare an X, he's going to get an error because it already exists. Now, in functions this won't matter so much, however here's something that will - if one scripter makes a function called "MyFunction" and another does the same - both unaware - your program giving an error when asked to run the function "MyFunction". By setting Modules, you're allowing numerous scripts to be loaded independently of each other, even if their names are the same. So how does it work? Let's take a look.

'inside frmMain

Dim S1 As MSScriptControlCtl.Module
DIm S2 As MSScriptControlCtl.Module

Private Sub FormLoad()
    Dim SharedThings as clsPublicFuncs
    scrMain.AddObject "Cls", SharedThings, True
    Set S1 = scrMain.Modules.Add("First")
    Set S2 = scrMain.Modules.Add("Second")
End Sub

Having done this, we can add code and execute code to each raw module, and they keep independent memory spaces for variables, etc - whilst still having acccess to whatever objects we've exposed them to. (IMPORTANT NOTE - If you were to place that AddObject line after the Modules.Add lines, you'd end up with two modules that were not exposed to the object in question. Each module's inheritance is based on the state of the state of the ScriptControl at the time.)

One last thing to mention is how you'd add code to individual objects, although I'm sure you can figure it out on your own by looking at the examples presenting thus far in the Tutorial! Here's some code we could slap after we've set S1 and S2 to be modules...

scrMain.Modules("First").AddCode Code
S1.Run "Main"
A slight change, but not by much. Oh, and if you need to reset everything on a script object, removing variables and code and the like, there's the lovely command "Reset" which exists in every ScriptControl object.

This pretty much wraps up the basics of ScriptControl objects! I hope you've found this useful to know, whether you decide to use this in your project or not. I would appreciate any feedback on this article - I'm happy to write more on other topics if I get some positive feedback.

For a functional example of how to use the tools we've discussed here, there's some example code available. It contains all the ideas here - I'm sure you can figure out how it works and what it does by fishing through the source!

--Q
HACK THE PLANET!