• 09/10/00 11:33 AM
    Sign in to follow this  

    Simple Game Scripting Part 1: Storing/Reading Your Scripts

    General and Gameplay Programming

    Myopic Rhino
    [size="5"]Copyright, etc.

    This article and all it's contents are (C) 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


    [size="5"]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().


    [size="5"]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.


    [size="5"]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.


    [size="5"]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.


    [size="5"]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.


    [size="5"]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

    [size="5"]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

    [size="5"]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

    [size="5"]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.


    [size="5"]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.


    [size="5"]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 ]


    [size="5"]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

    [email="dgoodlad@junction.net"][ dgoodlad@junction.net ][/email]

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

    David Goodlad


      Report Article
    Sign in to follow this  


    User Feedback

    Create an account or sign in to leave a review

    You need to be a member in order to leave a review

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

    There are no reviews to display.