Page 1 of 1

Basic Script Interpreter and Concepts

#1 modi123_1  Icon User is online

  • Suitor #2
  • member icon



Reputation: 8952
  • View blog
  • Posts: 33,558
  • Joined: 12-June 08

Posted 26 July 2013 - 01:34 PM

I recently found myself explaining to different pods of folks how to add 'scripting' to their games. The consistent response was confusion and bemoaning the difficulty of the task. Certainly there is some work to be done (if you want to add Lua, Javascript, Perl, Python etc) to your engine but it doesn't need to go _that_ far. With planned access points and a 'ninja' method to process what was entered you can have functional scripting with little hassle.

Topics covered:
- conceptually how to add scripting/modding ability to your game
- expanding on object interaction
- game loops

Technology used:
- VB.NET 4.0 framework

Notes
- Keep the images in the execution directory of the executable.
- the startup script file name is 'myscript.txt' - that also needs to be in the executable's directory.

What is scripting?

Scripting, in respects to a videogame, is a way to provide commands to the game engine that are interpreted and modify the state, properties of the actors, or generate new objects per their interpretation. If you make your game flexible enough you can enjoy a controlled avenue rich with development, content creation, and happy users without needing to recompile the game!

The gist is you review your game, determine what you would like to have affected by scripts, any actions for those affected areas, and then gin up some rules on matching unique command names and actions! It sounds complex, but it is not that bad.

Examples of games with scripting:
https://en.wikipedia...ted_video_games
http://nwn2.wikia.co...cript_directory

How does it relate to a video game?

When folks write their game (or game engine and game) there is a sort of tunnel vision. They see the game loop as a closed system, written in a specific language, and with specific states. There is no room to shoe horn in an 'on the fly' debug mode, scripts to modify game play (or test cases), or allow post modding of their game. All of this can happen, without the need to recompile, and on the fly!

What are common scripting points?
Typically there are two main script entry points into your game loop - at the start, or through a 'console input' by the user.

One would typically see a start-up script modify a large number of game environment options (such as level design, background, object start locations, etc) and is pretty complex.

A 'console input' is often for quick changes and are one liners. This would be things like turning on 'god mode' or 'no clip', unlocking a door, or advancing milestones in a preset quest.

People get bogged down in their game's complex states and ultimately are too consumed by the details that it seems impossible to wedge some sort of fancy script insert point into their loop, but this is not true.

Posted Image

If you look at a typical game loop there really only four major parts
- the start up
- the update
- the draw
- the exit

We know the start up scripts would be best in the first option, typically the check is best when the game window and objects are made, but before you kick off the timing engine. If the script needs to interact with objects (set locations, image, inventory, etc) it is best to change that before the engine starts processing updates.

The 'console input' is just a trigger off the key press event, right? A quick call to pause the game's update, input of the script command, process the input, and resume game play!

Conceptually it is difficult for people to get out of their engine's rut and see they can dynamically affect their game states, and objects, with little fuss.

The example:
Programmatically I am using my old game setup from this tutorial ( OOP with Video Game Basics Part 1 ). I modified the Entity class to have a few more directions of movement, and the user class to use those movements and have a method open to changing its color. The real magic is in the engine modifications.

The gist is the game starts up, loads an image, and moves the blue block around the screen without user input. I would like to change the path of the movement, color of the object, display information about the object, and turn on a universal boolean to illustrate affecting larger portions of the game.

Conceptually the format for the scripts were to be:
<action>:<modifier>

The action would be a single character would direct my interpreter to what action to perform, and the modifier is needed to say how much or to what.

The actions I would like to be able to access is:
d - direction of the block's movement.
-- -- Modifier is a value 0 through 8 representing one of the directions in the Entity.MovementDirection

c - color change.
-- -- Modifier is one of the strings: red, blue, or green. This would have the user object change the image/color accordingly.

p - print user information. Useful for debugging.
-- -- modifier: none.

g - 'god mode'. Use to be impervious to damage, enemies, etc. Functionally vacant with this demo, but it illustrates the global 'switch' system that can be built to react to user input.
-- -- modifier: 1 to turn on, anything else to turn off

I also envisioned stringing multiple script commands together separated by semicolons ( ; )

An example of:
c:red;d:1
means change the block's color to red, and its direction to 'up'.

A decent representation of in game object changes, visual changes, and game state changes!


Code
In the form's class here is the interpreter engine. It takes in a string, cleans it up, and attempts to fit it against the known scripting rules. Going forward I can add more interpreter rules here and not need to make crazy changes elsewhere!

To be clear - the console script commands and the 'start-up script' commands run on the same rules. They are just two different penetration points into the game loop but provide the same information!


  ''' <summary>
    ''' ParseScript takes in a potential script line and tries to match it up with a known script interpretation rule.
    ''' </summary>
    ''' <param name="input"></param>
    ''' <remarks></remarks>
    Private Sub ParseScript(ByVal input As String)
        Debug.WriteLine(String.Format("ProcessConsoleInput: {0}", input))

        Dim sTokens() As String '-- in case there are multiple lines on one line

        If input Is Nothing OrElse input.Trim = String.Empty Then Exit Sub '-- if nothing then do nothing.

        input = input.Trim.ToLower '-- to keep uniformity - throw everything to lower case

        sTokens = input.Split(CChar(";")) '-- you can string multiple scripted actions together by using a semicolon.. break those a part.

        For Each temp As String In sTokens

            If temp.Trim.Length > 0 Then

                '-- this quick scripting engine checks for the first letter.  If it is a known first letter then check the rest of the line for the action.
                Select Case temp(0)
                    Case "d"c '-- direction change
                        '-- The thought was to have a <character>:<action>
                        '-- in this case any number that corresponds to Entity.MovementDirection's number system works.
                        If Char.IsNumber(temp(2)) Then
                            If CInt(temp(2).ToString) >= 0 AndAlso CInt(temp(2).ToString) <= 8 Then
                                _eLastMove = CType(temp(2).ToString, Entity.MovementDirection)
                            End If
                        End If
                    Case "p"c '-- print character info
                        Debug.WriteLine(_oUser.Print())
                    Case "c"c '-- change color
                        If temp.Contains("red") Then
                            _oUser.LoadNewImage("red.png")
                        ElseIf temp.Contains("blue") Then
                            _oUser.LoadNewImage("blue.png")
                        ElseIf temp.Contains("green") Then
                            _oUser.LoadNewImage("green.png")
                        End If
                    Case "g"c '-- god mode - display text to show you are in 'god mode'.. flip a boolean 
                        If Char.IsNumber(temp(2)) Then
                            If CInt(temp(2).ToString) = 1 Then
                                _bGodModeOn = True
                            Else
                                _bGodModeOn = False
                            End If
                        End If

                End Select

            End If
        Next
    End Sub



Clearly some of that code can be tightened up, but you see how easy it is (with a switch statement and some if statements) to take gibberish input and enact sweeping changes under the game hood!


With respects to the penetration points there are two.

The first is the 'console input'. When the Keys.Oemtilde is pressed the game pauses, puts up an input box to ask for script input, processes the input found, and returns the game play. This means I can change mechanics on the fly!

    ''' <summary>
    ''' Important key command here.  When the user hits the tilde (~) pause the game, ask for input to pump through the scripting engine, and resume game play.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
        Select Case e.KeyCode
            Case Keys.Escape
                Me.Close()
            Case Keys.Space
                _bPause = Not _bPause

            Case Keys.Oemtilde'-- ask for console input
                _bPause = True

                ParseScript(InputBox("Console command", "Console Command"))

                _bPause = False

        End Select
    End Sub



The second input point is the startup. I am cleverly looking for a single script file called "myscript.txt" in the execution location of the game. If nothing is found then move on, but if it is found start reading each line and see what suggested changes can be made.

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.SetStyle(ControlStyles.UserPaint, True)
        Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)

        Me.Height = 500
        Me.Width = 500


        '-- create our user to be drawn.
        _oUser = New User
        _oUser.ScreenHeight = Me.Height '-- to prevent moving off the screen!
        _oUser.ScreenWidth = Me.Width '-- to prevent moving off the screen!

        '-- Important piece here - before the game engine starts, check to see if a script exists or not!
        CheckForScript("myscript.txt")

        '-- setup our timer as the engine to process.
        _timer = New Timer
        _timer.Interval = 100
        _timer.Start()
    End Sub


    ''' <summary>
    ''' Check a specific file location for a script file.  If said script file exists read each line and run it through the 'script parser'.
    ''' </summary>
    ''' <param name="location"></param>
    ''' <remarks></remarks>
    Private Sub CheckForScript(ByVal location As String)
        Dim sr As IO.StreamReader
        Dim sTemp As String

        Try
            '-- always check to see if the file exists
            If IO.File.Exists(location) Then
                '-- set up the reader.
                sr = New IO.StreamReader(location)

                Do
                    '-- read each line (maybe multiple commands per line?)
                    sTemp = sr.ReadLine
                    '-- try to process that script line
                    ParseScript(sTemp)
                Loop Until sTemp Is Nothing
            Else
                Debug.WriteLine("File does not exist! " + location)
            End If
        Catch ex As Exception
            Debug.WriteLine(ex.Message)
        End Try
    End Sub





Example screen shots:

Running normally

Posted Image

Console input of: c:green;d:4
Changes the color to green, and the direction.
Posted Image

Start script: g:1
starts game with 'god mode' on.
Posted Image

At the end - it is just this simple to get a script interpreter up and running in your game. Recognize the basic game loop, pick the two obvious entry points (script file or 'console input'), and structure your script rules. The rules are just products of thinking what you want to have external access to (images, object properties, levels, etc), and writing a quick rule-to-action interpreter for them!

Advanced topics:
- try adding python or lua to the mix
- expand the rule set to alter other game aspects
- try using multiple objects (with unique ids) and add to the interpreter function the ability to specify a single object (by its id) and have changes only affect that instance

The code behind of the form/game window.
Spoiler



Taking the advice of BetaWar (from this thread I decided to put the entire project on github. You can find it here: https://github.com/m..._game_scripting

Is This A Good Question/Topic? 4
  • +

Replies To: Basic Script Interpreter and Concepts

#2 aaron1178  Icon User is offline

  • Dovakiin, Dragonborn
  • member icon

Reputation: 169
  • View blog
  • Posts: 1,297
  • Joined: 22-October 08

Posted 26 July 2013 - 09:11 PM

Brilliant tutorial modi123_1. There are not a lot of tutorials that explain how a scripting engine is intertwined into a game. I've yet to start with a scripting engine in my game, but when I do, I will most likely be using python :) Keep up the good work.
Was This Post Helpful? 0
  • +
  • -

#3 modi123_1  Icon User is online

  • Suitor #2
  • member icon



Reputation: 8952
  • View blog
  • Posts: 33,558
  • Joined: 12-June 08

Posted 27 July 2013 - 12:50 AM

I am glad folks find it helpful. After the third or fourth explanation in as many weeks I felt I should codify the information in a common area.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1