'___ ' |he Code Post ' `-' Original Submission ' =============================================================== ' CONTRIBUTOR: Sj Zero ' DESCRIPTION: Short QB scripting tutorial ' DATE POSTED: Tue Aug 21 01:56:44 2001 ' =============================================================== BUILD Style Scripting By SJ Zero jkfirth@canadamail.com http://powerusr.freeyellow.com There aren't a lot of tutorials out there about scripting, and most of those just show the frame of mind, rather than actual commands, when describing the process. I'm going to try to be different. I'm going to explain how to make a scripting engine, and how you can invoke scripts in your game BUILD-Engine style, storing the locations and type of invokation using the NPC routines in your game. For the actual scripting system, I'll give code examples, but some of it will be in pseudocode. Section 1: Commands from a file For the longest time, I contemplated how to easily insert events into my game engine (which was fairly mature at the time, so a complete rewrite was out of the question). I didn't want to hard code them, since that would severely drive up the size of my source and executable, and make my code unbearable to work with, so I started brainstorming. My first idea was a seperate BASIC file which would be included and called upon whenever there was scripting to be done. This idea was thrown out, since the extra work wouldn't make the game any easier to work on. Then, I started thinking of a scripting engine. My first idea was to have everything in the file holding the scripts, be it locations, or other data -- all scripts would be stationary. This was a bad idea, I had decided, since I wanted moving NPCs, but I wasn't going to be able to bind scripts to NPCs, and this approach would mean a lot of hard drive activity, since the file would constantly be opened and checked for scripts every time the Player moved or pressed space. Heres my final solution: -All scripts are in one file -Every script has a header and a footer -The header is numbered to allow for fast seeking -If a script is to be run, an extra variable for each object on the screen is checked, and then the file is checked for that script number -Extra sprites were to be created, both would be invisible, one to signify when a player walked over a location to run the script, and the other to allow a script to be run when a player hits without a visible object. The code to read raw commands was easy. Heres an example that could easily be converted to use in a program. sub script(scriptno%) 'This is here because any given SLEEP sucks, and if you press space to enter the script, 'the space is still in the buffer, so this gives you a chance to move your hand off the key. FOR a = 1 TO 100 clearthebuffer$ = INKEY$ NEXT a fileno = FREEFILE FILE$ = "script.scr" OPEN FILE$ FOR INPUT AS #fileno 'Finds the record first. 'searches for "entry 1" or whatever. WHILE com$ <> "entry" + STR$(scriptno%) INPUT #1, com$ IF EOF(fileno) THEN CLOSE : END 'If it can't find the script, it exits. WEND WHILE com$ <> "end" 'This is where it searches for the end of the entry. INPUT #fileno, com$ 'a check for "entry" and "EOF" will work too. 'I check the first 5 letters of any command. SELECT CASE LCASE$(LTRIM$(RTRIM$(LEFT$(com$, 5)))) 'Five letters of every command are read. The rest are just for CASE "sleep" 'just sleep. 'user ease. The LTRIM and RTRIM let the scripting person use SLEEP 'tabs in the file to follow the same spacing rules programmers use. CASE "line1" 'line1 text string line1$ = MID$(com$, 6) CASE "line2" 'line2 text string line2$ = MID$(com$, 6) CASE "line3" 'line3 text string line3$ = MID$(com$, 6) CASE "line4" 'line4 text string line4$ = MID$(com$, 6) CASE "dialo" 'dialogbox 0 or 1 'Will fill the top of the screen with color X. lin% = VAL(MID$(com$, 10)) line (0, 0)-(319, 90), lin%, BF CASE "showt" 'showtext 0 or 1 IF VAL(RIGHT$(com$, 1)) = 1 THEN line (0, 0)-(319, 90), 1, BF locate 1,1 print line1$ Locate 2,1 print line2$ Locate 3,1 print line3$ Locate 4,1 print line4$ ELSE line (0, 0)-(319, 90), 0, BF END IF CASE "pause" otimer = TIMER lin = VAL(MID$(com$, 6)) WHILE TIMER < otimer + lin:WEND END SELECT WEND CLOSE END SUB I'll dissect the routine for you: -Opens file and searches for string -Checks first five letters of every line for a command. -if the letters spell sleep, if it reads that, it just runs the command. -for line1,2,3,4, it changes the string variable to that exists after the first 5 lines. -Dialogbox will draw that infamous blue box at the top of the screen. Be afraid. Be very afraid. -Showtext writes the text in line1,2,3,4 if the param is 1, otherwise it redraws the blue box. -Pause will just wait for X seconds. An entry in Script1.SCR would look something like this: entry 1 line1 This is a test line2 of the emergency broadcast system. line3 if this was more than a test line4 You'd be in a bunker, not reading this tutorial. dialogbox 1 showtext 1 end Section II This section should be shorter, since the concept is so simple. All you need to do here, is check the variables for all the objects you have on-screen and if you have an invisible sprite for placing both types of script (one for activated by space, and another for scripts objects activated by walking over it). All objects can have scripts bound the them if they are activated by pressing space, but the clear one does come in handy. I'm only going to cover the easiest way to do it, using a small piece of pseudocode/code. 'this is just a description of the structure. I can't remember how to do this 'in QB since most of the structs I've been doing lately have been in 'C(my latest engine in QB uses a 2d array to simulate a struct, and there are 'too many refrences to it to change that now...), so I may not get this right. type objects X as integer Y as integer sprite as integer scriptbinding as integer end type dim objs(1 to 100) as objects dim myX% dim myY% 'just a regular script check when we hit spacebar. 'since this is just a generic routine, direction checking isn't done. 'Therefore, you must be standing directly over the sprite to activate it. if inkey$ = " " then'just spacebar checking. for a = 1 to 100 'This would be in the player movement routine. if objs(a).sprite > 0 OR objs(a).sprite = -2 then 'Checks for the transparant if objs(a).X = myX% and objs(a).Y = myY% then 'object placement sprite if objs(a).scriptbinding <> 0 then script(objs(a).scriptbinding) end if end if end if next a end if 'It checks through the array to see if you are on any of them. 'Checks for scripts using that sprite we defined as 'walkoverable' and runs the correct script for a = 1 to 100 if objs(a).sprite = -1 then 'This number is just any number we chose to represent walk 'detection sprites. You touch em, they go off. if objs(a).X = myX% and objs(a).Y = myY% then if objs(a).scriptbinding <> 0 then script(objs(a).scriptbinding) end if end if end if next a 'Same here. It just checks the entire array for a match. This code should be almost self-explainitory. Most of it is just the collision detection any game engine should already have. Since the script bindings are held in the objects memory structures, it's a simple matter to work with them. The only difference between the two algorithms above is why the scripts are invoked (one is invoked because the user presses space, the other is invoked when the user walks over an object set to the -1 sprite). That's it for this tutorial. For my next game engine, I'm going to try to implement storage, decisions, mathematics, and loops in the scripting engine. If I achieve those goals. I'll write a tutorial on it. It would be easy to use named scripts (instead of script X%, it would be script text$, making it easier to work with the large file in huge projects). To implement that with the code I provided would be rather easy, just change the data types in the struct and on the script sub for the script, and remove the STR$() in the script sub, but keep in mind that working with strings is generally slower than working with integers. Later. Update: It turns out that there is one restriction on this code -- if you call the scripting routines using certain variables, you cannot change those variables using their names. EG. if you call a sub foo(x), and you call it foo bar, you won't be able to make any changes to foo, you'll have to change x. reply if that doesn't make sense, but I believe the feature is due to QBs recursion support...