Jump to content

  • Log In with Google      Sign In   
  • Create Account

We need your help!

We need 7 developers from Canada and 18 more from Australia to help us complete a research survey.

Support our site by taking a quick sponsored survey and win a chance at a $50 Amazon gift card. Click here to get started!


Like
0Likes
Dislike

Simple Game Scripting Part 1: Storing/Reading Your Scripts

By David Goodlad | Published Sep 10 2000 05:33 AM in Game Programming

#39 end file string script scripts series game array
If you find this article contains errors or problems rendering it unreadable (missing images or files, mangled code, improper text formatting, etc) please contact the editor so corrections can be made. Thank you for helping us improve this resource

Copyright, etc.

This article and all it's 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!

David Goodlad
dgoodlad@junction.net
webmaster: http://blackhole.thenexus.bc.ca


Introduction

Over the past few months, many people have been asking on message boards for help with scripting in their games (especially RPG's). This topic is an extremely important part of programming your game, yet it seems to be one of the least-documented aspects out there!

This series of articles will hopefully get you to the point where you can easily use custom scripting commands in your game to control any possible event you wish to occur.

A reminder, though, is that this is not necessarily the best, nor the fastest, possible way of adding scripting functionality. The code has been written in such a way that the concept should be easily understood so that you can design your own implementation which best suits your particular game. Also, the code requires Visual Basic 6; VB5 does not have a couple of the required functions such as Split() and CallByName().


Getting Started

The basic idea behind a custom scripting 'language' for your game is so that you do not have to hard-code every single possible event and conversation into your game. It also avoids the need to recompile the executable for the game each time you wish to slightly alter the storyline or for example the effects of drinking Potion X on a character with a 'Good' alignment.

For this first article in the series I will be explaining the general structure of your scripts, and how to store your scripts on disk, then read from them later.


Warning!

The method to read/write discussed in this article will leave your scripts easily editable by the end-user of your game. This is usually not desirable, so I suggest changing these later to implement some sort of custom resource-file setup that you should use for your graphics and sounds as well.


Script File Syntax/Format

Your scripts will be plain text, formatted with a very simple syntax:

CommandName (Parameter1,[Parameter2],[...])

Each line will have this simple format. There is an exception, though. You will want to be able to define blocks of these commands (I will go into why later). So, to define such a block:

*Block* BlockType BlockName
...CommandsHere...
*EndBlock* BlockName

For example, if there is a block type called 'Event':

*Block* Event Foo
Bar ("asdf")
*EndBlock* Foo

All commands must be contained in 'Blocks', even if there is only one of them. The reasoning for this will become clear later; for now just accept my words as the truth :)

This is the simple structure of your scripts. It may not seem very useful/extensible right now, but later on you will understand. You will just have to trust me about this, just as with the rule about commands having to be within blocks.


Loading Your Scripts

There is no real need to discuss the writing of your script files from VB, as you can simply use notepad or another plain-text editor to do this.

But, loading your script files into your game for use is another story. There are numerous questions which can be asked about how to do this, such as how the lines of 'script' can be parsed into separate pieces, and how to store the scripts while in memory.


User-Defined Types - Gotta Have 'Em!

The first thing to be done is define a user-defined type for a script command line. I suggest placing this into a class module, probably called something such as CScriptParser in order to allow for further expansion in later parts of this series of articles.

Private Type tCmdLine
Command As String
Parameters() As String
End Type

This should be placed in the declarations section of your class module. Another useful UDT that should be created is one for an event block:

Private Type tCmdBlock
BlockType As String
BlockName As String
Commands() As tCmdLine
End Type


Storage Variables

To prepare for storing your scripts, you'll need a variable array (put it into your declarations section of the class module you created earlier as well):

Dim CommandBlocks(1023) As tCmdBlock, iLastBlock As Integer

This creates a limit of 1024 possible blocks loaded simultaneously. You can always increase this if you need to. The iLastBlock variable is simply to keep track of the last element in the array which is occupied. You should initialize this variable to -1 when an instance of the class is created, so in the Class_Initialize method of your class, put:

iLastBlock = -1


Actually Loading the Script File

Now that we've got all of our storage structures created, we can actually load our script file. Put this function into your class module:

Public Function LoadScriptFile(sFileName As String) As Integer
  'If the file is empty/doesn't exist, don't open it 
  'and exit the function with a return value of 1.
  If FileLen(App.Path & "\" & sFileName) = 0 Then
	LoadScriptFile = 1
	Exit Function
  End If

  'sTemp: a temporary string variable - stores the input from the file
  'bEndLoop + bEndLoop2: booleans for controlling when to stop reading
  'aTemp(): array of strings for splitting up parameters.
  Dim sTemp As String, bEndLoop As Boolean, bEndLoop2 As Boolean
  Dim aTemp() As String

  'Initialize the boolean control variables
  bEndLoop = False
  bEndLoop2 = False

  'Load up the specified script file
  Open App.Path & "\" & sFileName For Input As #1

  Do
	'Input a line from the file into sTemp
	Line Input #1, sTemp

	'This is to check whether this is the end of the file or not...
	'Checks against everything before the first occurance of a 'space'
	Select Case Left$(sTemp, InStr(sTemp, " ") - 1)
	'If we're at the end of the file set the flag to end the reading loop
  	Case "*EOF*"
    	bEndLoop = True
    	'Otherwise we're at the start of a block...
  	Case "*Block*"
    	iLastBlock = iLastBlock + 1
    	'Remove the "*Block* " at the beginning of the string
    	sTemp = Right$(sTemp, Len(sTemp) - Len("*Block*") - 1)
    	'Put everything before the first 'space' character into the BlockType
    	CommandBlocks(iLastBlock).BlockType = Left$(sTemp, InStr(sTemp, " ") - 1)
    	'Put everything after the first 'space' character into the BlockName
    	CommandBlocks(iLastBlock).BlockName = Right$(sTemp, Len(sTemp) - InStr(sTemp, " "))
    	With CommandBlocks(iLastBlock)
    	'Initialize the Commands() array
      	ReDim .Commands(0)
      	Do
        	'Read a command line
        	Line Input #1, sTemp
        	'If we're at the end of the block...
        	If sTemp = "*EndBlock* " & .BlockName Then
          	'Get rid of the empty element of the Commands() array
          	ReDim Preserve .Commands(UBound(.Commands()) - 1)
          	'Set the boolean control variable to exit the inner loop
          	bEndLoop2 = True
        	Else
          	'Read in the command name as everything before the left bracket
          	.Commands(UBound(.Commands())).Command = Left$(sTemp, InStr(sTemp, " (") - 1)
          	'Split up everything between the left and right brackets using commas
          	aTemp() = Split(Mid(sTemp, InStr(sTemp, "(") + 1, Len(sTemp) - InStr(sTemp, "(") - 1), ",")
          	'Increase the Parameters() array's size to how many parameters there are
          	ReDim .Commands(UBound(.Commands())).Parameters(UBound(aTemp))
          	'Copy the parameters from the 'splitted' array
          	.Commands(UBound(.Commands())).Parameters() = aTemp()
          	'Add an empty element to the Commands() array
          	ReDim Preserve .Commands(UBound(.Commands()) + 1)
        	End If
      	Loop Until bEndLoop2
      	'Reset the control variable
      	bEndLoop2 = False
    	End With
  	Case Else
    	'This should *never* happen because all commands
    	'should be contained within blocks.
    	Beep
    	LoadScriptFile = 1
    	Exit Function
	End Select
  Loop Until bEndLoop
  Close #1
  'Return a value of 0
  LoadScriptFile = 0
End Function


An explanation!

Hopefully the comments in the code for the LoadScriptFile function are sufficient to explain what the more confusing lines do. The use of string manipulation functions such as InStr, Left$, and Right$ can become very confusing if you have not used them before, or don't know how they work. Therefore, if you wish to truly understand how the string parsing works, I suggest you read up on these functions, and other related ones including Mid and Split.


Until next time...

By now, you should understand how to load and store your script files on disk and in memory. This is the most important piece, and can be the most complicated. The actual loading and splitting up of the individual lines is integral to the use of your scripting engine, and thus you have to make absolutely sure that it is foolproof (or as close to this as possible!). I suggest adding error handlers, and using the return value of LoadScriptFile to check whether the file was loaded correctly when you call it.

In the next article in this series, the actual use of your scripting will be discussed. The third part of this series will then continue along that theme and go into much more depth with the implementation, including a section on interfacing the scripting engine with a tile-based system in order to create events.


Source Code

Though simple, if you would like the source code to this part of the series, the class module is available at:

[http://blackhole.the...criptParser.cls ]


Contact + Future Releases

Updated versions of this article, as well as the later parts of the series as they are written, will be available from my website, the black hole.

[http://blackhole.thenexus.bc.ca/ ]

I can be reached for comments or questions at

[ dgoodlad@junction.net ]

I sincerely hope you've gained from this article, and look forward to the next part of the series!

David Goodlad





Comments

Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.




PARTNERS