Simple Game Scripting Part 3: Control Stuctures for your Scripting Language
This article and all its contents are © 2000 David Goodlad. They may not be reproduced in any form without permission from the author.
I will very gladly give permission to copy the article, though, so send me an email!
Welcome to the third part of this series of articles about what should be recognized as one of the most important pieces of game programming: the addition of a simple scripting lanugage. In this part the focus will be on the implementation of control structures into your language. They will all be coded as regular commands, written in the same way as you would implement any other custom commands for your language. A reminder that, as usual, the actual implementation of all of these things was done to maintain the ability to be understood by you people easily. Therefore, I admittedly have made the functions somewhat 'klunky' and not necessarily the best they can be. A bigger explanation of what can be done about this is in the conclusion... But, remember, these examples are designed to be learned from, not copied, because they will not work very well in practical uses. But, anyways, enough chatter, let's get on with it!!
The first requirement
Before we can actually start making 'decisions' in the language, there must be some way for a script to set and/or retrieve variables: in this case, bit flags. These have been chosen for simplicity; they are simple boolean values stored in a 'compressed' format, so that you can have lots of them. You would probably want named 'variables' in your actual game, but to keep things easy to understand, I'll stick with bit flags for now.
Bit flags are simple to implement. The actual reasons for it working are out of the scope of this article, so I won't bother explaining really. But, here are three functions, one which sets a flag, one which unsets a flag, and one which returns the value of a flag, as well as the definition for the 'Flags' array. As usual, these should be put in your CScriptParser class.
If it's not self-evident to you, there are 8 bits per 'Flags' array item, numbered 0 to 7. To call these functions from your script, simply use the format below:
So, for example, you wanted to set bit number 5 of flag 17. You would call:
Not hard, huh? Now that we've got that down, time to implement a simple If statement!
As you should know, If statements have at least two parts to them: a condition, and a command to execute if this condition is met. They can also have a third part (the 'Else' in VB), which is executed if the condition is NOT met. The following function, when added to your CScriptParser class will add the ability to decide which of two command blocks to execute (or just whether or not to execute one) depending on the condition of a flag.
(Gee, I like that heading don't I! lol) Anyways, that function should be pretty self-explanatory with the comments in it. All that it does is this:
One final note about this implementation of the If statement: it is not like the VB if statement where you place it 'around' your commands that you want conditionally executed. You tell it which command blocks to execute when the condition is true or false, in one line, like so:
This would check if bit 7 of flag 5 was true; if it is, then execute the command block 'blocktrue', otherwise execute the commandblock 'blockfalse'.
Now that we have our first big feat out of the way, the If statement, we can move on to some basic loops. In many ways, looping is actually easier than the If statement that we just learned. So if you got through that last page okay, you will be just fine from here-on-out! :)
The for loop is one of the most often-used loops in VB for games, so we'll start with that. Basically, all that you want it to do is execute a command block a specified number of times. Here is a basic implementation of a simple For loop:
Simple, huh? Basically, this function becomes a 'wrapper' for vb's native For loop, making life simple on you, the coder. One thing to be noticed, though, is that the command block has no way of knowing the contents of the 'counter' variable, i. Implementing this is something that you should try as an exercise. I would suggest waiting on this, though, until I discuss later possible methods of implementing true, named variables.
Do...Loop Until Loops
This type of loop is also quite simple to implement, again 'wrapping' vb's built-in loop of the same name. Look this code over:
Again, this function should be fairly straightforward. The only 'new' thing is the ability for the script to specify whether to check for the flag being "on" (1) or "off" (0) as the fourth parameter, a(3).
More theory time!
Now, after giving you all that ugly source code to figure out how to implement more efficiently, I will move on to some theory, and let you decide how you want to code it; I won't provide any code in the article for this little discussion.
Now, you're probably curious as to why I have used only bit flags for data-storage in the scripts so far. With a bit of thought, however, the question arises "How can we name a variable, and refer to it as a string?" In other words, how can a script define a variable by itself, and then have vb refer to that variable using a STRING, because the actual VB code can never actually contain the name of that variable, because it was written in the script, not the vb code! A C/C++ coder would immediately jump to something like a linked list, but we can't do this in VB... Or can we? VB can't do it in the usual manner of C/C++, using a struct, the C equivalent of a user-defined type in Visual Basic.
Once again, the magic that is VB's poorly designed, poorly implemented OOP system comes to our rescue. Using a system of classes, we can design a linked list fairly easily. You would have one class, called something like 'CLinkedListItem', and it would have a member variable called, for example, 'm_Next', defined as a CLinkedListItem object. In this way, you can set the m_Next variable to be a 'pointer' to the next item in the list! Then, another variable could be created called 'm_Name' which would contain the name of the variable in the context of the script. A member variable named 'm_Data' would be needed to hold the 'data' of the script variable.
With this type of setup, it would be quite simple to then read up on binary trees, or hashing algorithms, and implemente one of these methods to allow for efficiency in searching for the correct element of the linked list. An exellent resource for this type of thing is at http://members.xoom.com/thomasn/s_man.htm. He implements the algorithms in both C and VB, so you should be able to do something with it!
Using named variables will come in very handy with your scripting language, making it much easier to keep track of what variable contains what data :) With the numbered bits/flags system, you would need to keep some sort of record of what bit/flag represented what.
As in the previous article, I will leave you with some suggestions for things to do to improve the scripting system for your own personal needs.
I mentioned in the introduction that the implentation of all the loops etc. was not very well done (ie: efficient). They were also somewhat restrictive, and could become quite complicated due to the need for making command blocks 'become' part of a larger one, because the small ones would be part of conditional statements and loops, whereas the logical thing in everyone's mind is that of VB, having the actual statements 'inside' beginning and ending statements for the control structure, whether it be a For loop, of and If...Then...Else statement.
I believe that if you wanted to set this up better, which I really think you should, you could change the layout of the actual script loading script, and the data structures that store the command blocks etc. I am sure that lights are starting to flash on in your head by now, so I will leave this discussion at that, and allow it to progress to your own personal taste.
Well, I finally got this third part done. It was a push, as you can probably tell. I hope it has gone into enough depth while retaining enough generality to allow you to take everything to your own personal liking. You should, by now, have a pretty good understanding of how the basic system works, including how to create your own scripting commands. I would really like to make one thing clear, though. When I wrote all of this code, not only for this part of the series, but for all of them, I did not truly mean for the code to be copied and pasted, and actually used. I mainly intended it, and still do intend it, to be used more as an explanatory tool, and to demonstrate one way of doing things (which, imho, is a very ugly way of actually implementing the scripting system). Therefore, none of the code that has been written for this article is set in stone, and neither is the entire structure of the scripting system. Please feel very free to change things: how the scripts are stored in memory during run-time, add extra 'built-in' commands that aren't run like the normal ones with ExecuteCmdBlah. There are a million possible, and very viable, solutions to all the problems with this system, and I am sure that you can come up with at least one way to fix any problem that you run into!
So, basically what I am saying is, BE INVENTIVE AND CREATIVE. Do what suits YOU, not what you think I would have done. That's how you become a better programmer! :)
The source code to this third part in the series is, as always, available from my website. A change from previous parts, though, is that there is more to it than a single class module. I have included a sample project, as well as a somewhat confusing demonstration script. This is what I used to test all the code, so it's not exactly well commented in itself. But, by stepping through it, and watching what happens, you'll pick it up in no time!
The address of the ZIP file containing the source code is:
Contact + Future Releases
As before, updated versions of this third part of the series, as well as later parts as they are written, will be available from my website, the black hole:
I welcome any questions or comments, as well as *constructive* criticism :) I can be reached via email at:
Thanks for reading!