Chapter 13. Using Portals to Expand the World

Up to this point, we have learned quite a bit about creating a game world, and we just started to interact with the game world in the previous chapter. We need to bump it up a notch now by adding to both the editor and the game project the ability to create and use portals. A portal is an opening that connects two worlds or allows someone to cross over from one area into another. In terms of a tiled game world, we need a portal that will teleport the player to another location on the map, as well as specify a position on a different level file entirely. Once the code to teleport to a new location on a map is understood, adding the ability to teleport to a new level is just a matter of loading that new level file with the Level class and then setting the scroll position to the target X,Y position. Fortunately, a new version of the level editor is available with fields for portals built in to the tile structure which we will examine in this chapter.

Here’s what we’ll cover in this chapter:

Updating the Level Editor

It’s time for an update to the level editor to accommodate new features needed for this chapter. The editor project is available in the chapter’s resource files with the full source code, but we won’t be going over the sources here due to space concerns, and because the editor is being written in C#, for reasons stated back in Chapter 10, “Creating the Level Editor.”

The file extension for level editor files is now .level. Although the content is still pure XML, a custom extension gives our editor more of a professional edge and helps with identifying level files in a directory full of other game files. Unfortunately, due to the many new fields in the new editor, it is no longer compatible with old level files from the previous version of the editor. If this was a production editor used to make games and an update was made to it, then of course we would want to provide an importer for the older level file format. But since this is a pre-alpha work in progress, that’s not something you want to spend time doing with pre-release software—and an internal tool at that.

Perusing New Editor Features

Figure 13.1 shows the new version of the editor enhanced for the needs of this chapter. Note that the lonely “Data1” field and “Collidable” checkbox are no longer at the bottom of the form, but in the lower-left corner with some new fields. Also, the tile palette on the left has been shrunk a bit—it now affords a smaller selection area, but that is no problem due to the scrollbar.

The new level editor has a cleaner user interface.

Figure 13.1. The new level editor has a cleaner user interface.

I recall mentioning back in Chapter 10 that the early editor was already sufficient for making a tiled game world with collision, portals, and even item drops, through creative use of the single Data1 field. While that is true, there’s no need to limit our tile data to just one shared field when we can use any number of fields—maybe even a whole secondary form filled with possible data for each tile! The .xml file format can handle it, and even 100 pieces of data for each tile would not lead to much slower load times. But, the fact is, we don’t need that many fields. The four generic data fields store strings so you can put any data you want there—whole numbers, decimal numbers, text descriptions, etc. You could use one as a searchable item name field and then add an item to the game world at that tile location, or even something as exotic as a script function name. Here are the data fields for each tile:

  • Tile palette number

  • Data 1

  • Data 2

  • Data 3

  • Data 4

  • Collidable

  • Portal

  • Portal X

  • Portal Y

  • Portal file

Creating a Portal

A portal is a doorway to another dimension. Or, in the case of our editor here, a new x,y location on the map. Or on another map file! Let’s start with a series of portals on a single map first, and then look at how to portal to another world. Take a look at the data for the highlighted tile in Figure 13.2. The Portal flag is checked, while the x and y fields are set to coordinates (101,16).

Creating a portal using the new tile data fields.

Figure 13.2. Creating a portal using the new tile data fields.

The location (101,16) is on the right side of the map, shown in Figure 13.3. What we want to do is have the game jump to that location when our character walks into the portal tile. Nearby, the target location is another portal tile.

The target location of the first portal on the map is (103,16).

Figure 13.3. The target location of the first portal on the map is (103,16).

In case you are wondering why the two portals aren’t linked directly together, that is something you can play with if you want, but if you point one portal to a tile that contains another portal, then your character will teleport twice. Unless you want that kind of behavior, don’t link portal squares directly—have one drop off the player nearby but not directly on another portal. Or, go ahead and do it and see for yourself what happens! In our example, you must press Space to trigger a portal, but if you use automatic teleporting then the player could be teleported repeatedly, possibly even getting stuck in a portal loop.

It’s a Data-Driven Game World

In the previous chapter, we learned how to create a list of tree sprites and draw them in the game world, so that when the player moves around the trees come into view within the scrolling viewport. That works well when you want to scatter random environment items like bushes, houses, coffee shops, software stores, and, well, anything you want. We can also use the level editor to position objects at a specific location, which is more useful than using randomness, especially when you need to count on a certain thing being at a certain location. For instance, you might have a quest that has the player find a certain landmark where nearby a treasure is buried.

The data fields are numbered 1 to 4, and can contain any type of data—numbers or strings. If we used these fields to position an item, we could use them like so:

Data 1: Item number

Data 2: Position x

Data 3: Position y

Data 4: Script function

The item number would be out of the game item database, or it could be the number or name of a sprite. The x,y position of the item is next in Data 2 and 3. The fourth one is a bit interesting. What is a script function? This goes a bit beyond the scope of this book, but if we wanted to really make this level editor and game engine interesting, we could add Lua script support to the game. Lua is an interpreted programming language—meaning, Lua source code is not compiled, it is simply stored in a text file and treated as text, and yet the Lua interpreter will run our script code at runtime. The ramifications for scripting are enormous. Imagine being able to edit a script without restarting the game. Yes, that’s possible: edit the script, save it, then load and execute the script with the Lua interpreter. Like I said, this gets a bit complicated but it adds a tremendous amount of design freedom to the game, which is otherwise bound by its data and engine. We will add Lua scripting support to the game in the next section.

Now we need to look at some Basic code to make this all work. Among other things, we have some work to do in the loadTilemap() function now because of the new fields.

Level Class Modifications

We need to make a few new changes to the Level class (Level.vb) to accommodate the changes required for this chapter. First, the new data fields need to be added to the tilemapStruct structure, and then we need to add a function to return tile data at a specified index in the tilemap so that the player’s character sprite can interact with the level. In other words, we need to find out where the portals are and let the character get teleported! Here is the new Level class with an expanded tilemapStruct and loadTilemap() function. There is also a new set of getTile() functions, a GridPos property used as an alternative to ScrollPos.

Imports System.Xml
Public Class Level
    Public Structure tilemapStruct
        Public tilenum As Integer
        Public data1 As String
        Public data2 As String
        Public data3 As String
        Public data4 As String
        Public collidable As Boolean
        Public portal As Boolean
        Public portalx As Integer
        Public portaly As Integer
        Public portalfile As String
    End Structure
    Private p_game As Game
    Private p_mapSize As New Point(0, 0)
    Private p_windowSize As New Point(0, 0)
    Private p_tileSize As Integer
    Private p_bmpTiles As Bitmap
    Private p_columns As Integer
    Private p_bmpScrollBuffer As Bitmap
    Private p_gfxScrollBuffer As Graphics
    Private p_tilemap() As tilemapStruct
    Private p_scrollPos As New PointF(0, 0)
    Private p_subtile As New PointF(0, 0)
    Private p_oldScrollPos As New PointF(-1, -1)

    Public Function getTile(ByVal p As PointF) As tilemapStruct
        Return getTile(p.Y * 128 + p.X)
    End Function

    Public Function getTile(ByVal pixelx As Integer, _
            ByVal pixely As Integer) As tilemapStruct

    Return getTile(pixely * 128 + pixelx)
End Function

Public Function getTile(ByVal index As Integer) As tilemapStruct
    Return p_tilemap(index)
End Function

REM get/set scroll position by whole tile position
Public Property GridPos() As Point
    Get
        Dim x As Integer = p_scrollPos.X / p_tileSize
        Dim y As Integer = p_scrollPos.Y / p_tileSize
        Return New Point(x, y)
    End Get
    Set(ByVal value As Point)
        Dim x As Single = value.X * p_tileSize
        Dim y As Single = value.Y * p_tileSize
        p_scrollPos = New PointF(x, y)
    End Set
End Property

REM get/set scroll position by pixel position
Public Property ScrollPos() As PointF
    Get
        Return p_scrollPos
    End Get
    Set(ByVal value As PointF)
        REM save new scroll position
        p_scrollPos = value
    End Set
End Property

Public Sub New(ByRef gameObject As Game, ByVal width As Integer, _
        ByVal height As Integer, ByVal tileSize As Integer)
    p_game = gameObject
    p_windowSize = New Point(width, height)
    p_mapSize = New Point(width * tileSize, height * tileSize)
    p_tileSize = tileSize
    REM create scroll buffer
    p_bmpScrollBuffer = New Bitmap(p_mapSize.X + p_tileSize, _

        p_mapSize.Y + p_tileSize)
    p_gfxScrollBuffer = Graphics.FromImage(p_bmpScrollBuffer)
    REM create tilemap
    ReDim p_tilemap(128 * 128)
End Sub

Public Function loadTilemap(ByVal filename As String) As Boolean
    Try
        Dim doc As XmlDocument = New XmlDocument()
        doc.Load(filename)
        Dim nodelist As XmlNodeList = doc.GetElementsByTagName("tiles")
        For Each node As XmlNode In nodelist
          Dim element As XmlElement = node
          Dim index As Integer = 0
          Dim ts As tilemapStruct
          Dim data As String

          REM read data fields from xml
          Data = element.GetElementsByTagName("tile")(0).InnerText
          index = Convert.ToInt32(data)
          data = element.GetElementsByTagName("value")(0).InnerText
          ts.tilenum = Convert.ToInt32(data)
          data = element.GetElementsByTagName("data1")(0).InnerText
          ts.data1 = Convert.ToString(data)
          data = element.GetElementsByTagName("data2")(0).InnerText
          ts.data2 = Convert.ToString(data)
          data = element.GetElementsByTagName("data3")(0).InnerText
          ts.data3 = Convert.ToString(data)
          data = element.GetElementsByTagName("data4")(0).InnerText
          ts.data4 = Convert.ToString(data)
          data = element.GetElementsByTagName("collidable")(0).InnerText
          ts.collidable = Convert.ToBoolean(data)
          data = element.GetElementsByTagName("portal")(0).InnerText
          ts.portal = Convert.ToBoolean(data)
          data = element.GetElementsByTagName("portalx")(0).InnerText
          ts.portalx = Convert.ToInt32(data)
          data = element.GetElementsByTagName("portaly")(0).InnerText
          ts.portaly = Convert.ToInt32(data)
          data = element.GetElementsByTagName("portalfile")(0).InnerText
          ts.portalfile = Convert.ToString(data)

          REM store data in tilemap
          p_tilemap(index) = ts
        Next
    Catch es As Exception
        MessageBox.Show(es.Message)
        Return False
    End Try
    Return True
End Function

Public Function loadPalette(ByVal filename As String, _
        ByVal columns As Integer) As Boolean
    p_columns = columns
    Try
        p_bmpTiles = New Bitmap(filename)
    Catch ex As Exception
        Return False
    End Try
    Return True
End Function

Public Sub Update()
    REM fill the scroll buffer only when moving
    If p_scrollPos <> p_oldScrollPos Then
        p_oldScrollPos = p_scrollPos
        REM validate X range
        If p_scrollPos.X < 0 Then p_scrollPos.X = 0
        If p_scrollPos.X > (127 - p_windowSize.X) * p_tileSize Then
            p_scrollPos.X = (127 - p_windowSize.X) * p_tileSize
        End If
        REM validate Y range
        If p_scrollPos.Y < 0 Then p_scrollPos.Y = 0
        If p_scrollPos.Y > (127 - p_windowSize.Y) * p_tileSize Then
            p_scrollPos.Y = (127 - p_windowSize.Y) * p_tileSize
        End If
        REM calculate sub-tile size
        p_subtile.X = p_scrollPos.X Mod p_tileSize
        p_subtile.Y = p_scrollPos.Y Mod p_tileSize
        REM fill scroll buffer with tiles

            Dim tilenum, sx, sy As Integer
            For x = 0 To p_windowSize.X
                For y = 0 To p_windowSize.Y
                   sx = p_scrollPos.X  p_tileSize + x
                   sy = p_scrollPos.Y  p_tileSize + y
                   tilenum = p_tilemap(sy * 128 + sx).tilenum
                   drawTileNumber(x, y, tilenum)
                Next
            Next
        End If
    End Sub

    Public Sub drawTileNumber(ByVal x As Integer, ByVal y As Integer, _
            ByVal tile As Integer)
        Dim sx As Integer = (tile Mod p_columns) * (p_tileSize + 1)
        Dim sy As Integer = (tile  p_columns) * (p_tileSize + 1)
        Dim src As New Rectangle(sx, sy, p_tileSize, p_tileSize)
        Dim dx As Integer = x * p_tileSize
        Dim dy As Integer = y * p_tileSize
        p_gfxScrollBuffer.DrawImage(p_bmpTiles, dx, dy, src, _
            GraphicsUnit.Pixel)
    End Sub

    Public Sub Draw(ByVal rect As Rectangle)
        Draw(rect.X, rect.Y, rect.Width, rect.Height)
    End Sub

    Public Sub Draw(ByVal width As Integer, ByVal height As Integer)
        Draw(0, 0, width, height)
    End Sub

    Public Sub Draw(ByVal x As Integer, ByVal y As Integer, _
            ByVal width As Integer, ByVal height As Integer)
        REM draw the scroll viewport
        Dim source As New Rectangle(p_subtile.X, p_subtile.Y, _
            width, height)
        p_game.Device.DrawImage(p_bmpScrollBuffer, x, y, source, _
            GraphicsUnit.Pixel)
    End Sub
End Class

Teleporting to Another Dimension

The first thing we need to do to get portals working is to isolate the portion of the character sprite that is actually on the “ground,” so to speak. By default, the player sprite (which is called hero in our code) is positioned on the screen in the upper-left corner. Since the sprite is 96 × 96 pixels in size, there’s a lot of area taken up by the sprite that is much larger than the 32 × 32 tiles. If we use the upper-left corner, then the player will be interacting with tiles on the ground from a position above and to the left of his or her head! That definitely won’t work. So, we need to adjust the position used to determine what tile the player is walking on—we need to isolate the player’s feet. Figure 13.4 shows the collision boxes for the player sprite. The blue box represents the entire character’s collision box, while the small red box (and red dot) represent the walking collision box.

Isolating the player sprite’s “foot” contact with the ground.

Figure 13.4. Isolating the player sprite’s “foot” contact with the ground.

The small red collision box, and the red dot at its center, is what we actually want to use as a center point to determine which tile the sprite is “walking on.” Thus, when the player walks onto a portal tile, it will accurately look as if the sprite’s feet touched the tile before the teleport occurred. The Portal Demo program looks at that coordinate as a position relative to the scroll position and then retrieves the data for the tile at that location. Figure 13.5 shows information about the portal tile the player is standing on—note the message in the upper-left corner of the window.

Detecting when the player walks on a portal tile.

Figure 13.5. Detecting when the player walks on a portal tile.

In the game, it’s up to you how the portals will work. You can make them automatically teleport the player just by merely walking on the tile, or you can require the player to take some action—perhaps using an item to trigger the portal. In our program, the Space key is the trigger. When the portal is engaged, the player is teleported to the target coordinate (101,16), as shown in Figure 13.6.

The portal has sent the player across the map!

Figure 13.6. The portal has sent the player across the map!

Trick

Getting tired of the same old ground tiles in every example? Replace them! You are encouraged to use a different set of ground tiles or add news ones to this collection. I am only using these same tiles for consistency. You may replace the tiles in the level editor and in the game. The only requirement is that your tile palette image be oriented like the one presented in the book and that the tiles remain at 32 × 32 pixels in size. Otherwise, some coding changes will be needed.

Looking for Tile Collisions

The Portal Demo program also looks for the Collidable property in tiles and reports on the screen when a collidable tile is identified. Figure 13.7 shows the message that is printed when the player walks over a collidable tile. Although the sprite doesn’t respond to collidable tiles yet in this example, we can use this information to enable collision response in the next major revision to the game.

Detecting collidable tiles.

Figure 13.7. Detecting collidable tiles.

Hint

This quick example is not quite polished yet, so expect to see some jittery sprites and timing problems. The point is to get these features to work first, and then make them work great afterward!

Portal Demo Program

Here is the source code for the Portal Demo program.

Public Class Form1
    Public Structure keyStates
        Public up, down, left, right As Boolean
    End Structure
    Private game As Game
    Private level As Level
    Private keyState As keyStates
    Private gameover As Boolean = False
    Private hero As Sprite
    Private heroDir As Integer = 0
    Private portalFlag As Boolean = False
    Private portalTarget As Point

Private Sub Form1_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
    Me.Text = "Portal Demo"
    REM create game object
    game = New Game(Me, 800, 600)
    REM create tilemap
    level = New Level(game, 25, 19, 32)
    level.loadTilemap("portals.level")
    level.loadPalette("palette.bmp", 5)
    REM load hero
    hero = New Sprite(game)
    hero.Image = game.LoadBitmap("hero_sword_walk.png")
    hero.Columns = 9
    hero.TotalFrames = 9 * 8
    hero.Size = New Point(96, 96)
    hero.Position = New Point(400 - 48, 300 - 48)
    hero.AnimateWrapMode = Sprite.AnimateWrap.WRAP
    hero.AnimationRate = 20
    REM game loop
    While Not gameover
        doUpdate()
    End While
End Sub

Private Sub Form1_KeyDown(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.KeyEventArgs) _
        Handles Me.KeyDown
    Select Case (e.KeyCode)
        Case Keys.Up, Keys.W : keyState.up = True
        Case Keys.Down, Keys.S : keyState.down = True
        Case Keys.Left, Keys.A : keyState.left = True
        Case Keys.Right, Keys.D : keyState.right = True
    End Select
End Sub

Private Sub Form1_KeyUp(ByVal sender As System.Object, _
        ByVal e As System.Windows.Forms.KeyEventArgs) _
        Handles MyBase.KeyUp
    Select Case (e.KeyCode)
        Case Keys.Escape : End

        Case Keys.Up, Keys.W : keyState.up = False
        Case Keys.Down, Keys.S : keyState.down = False
        Case Keys.Left, Keys.A : keyState.left = False
        Case Keys.Right, Keys.D : keyState.right = False
        Case Keys.Space
            If portalFlag Then
                level.GridPos = portalTarget
            End If
    End Select
End Sub

Private Sub doUpdate()
    REM move the tilemap scroll position
    Dim steps As Integer = 8
    Dim pos As PointF = level.ScrollPos

    REM up key movement
    If keyState.up Then
        If hero.Y > 300 - 48 Then
            hero.Y -= steps
        Else
            pos.Y -= steps
            If pos.Y <= 0 Then
                hero.Y -= steps
            End If
        End If
    REM down key movement
    ElseIf keyState.down Then
        If hero.Y < 300 - 48 Then
            hero.Y += steps
        Else
            pos.Y += steps
            If pos.Y >= (127 - 19) * 32 Then
                hero.Y += steps
            End If
        End If
    End If
    REM left key movement
    If keyState.left Then
        If hero.X > 400 - 48 Then

        hero.X -= steps
    Else
        pos.X -= steps
        If pos.X <= 0 Then
            hero.X -= steps
        End If
    End If
REM right key movement
ElseIf keyState.right Then
    If hero.X < 400 - 48 Then
        hero.X += steps
    Else
        pos.X += steps
        If pos.X >= (127 - 25) * 32 Then
            hero.X += steps
        End If
    End If
End If

REM update scroller position
level.ScrollPos = pos
level.Update()

REM limit player sprite to the screen boundary
If hero.X < -32 Then
    hero.X = -32
ElseIf hero.X > 800 - 65 Then
    hero.X = 800 - 65
End If
If hero.Y < -48 Then
    hero.Y = -48
ElseIf hero.Y > 600 - 81 Then
    hero.Y = 600 - 81
End If

REM orient the player in the right direction
If keyState.up And keyState.right Then
    heroDir = 1
ElseIf keyState.right And keyState.down Then
    heroDir = 3

ElseIf keyState.down And keyState.left Then
    heroDir = 5
ElseIf keyState.left And keyState.up Then
    heroDir = 7
ElseIf keyState.up Then
    heroDir = 0
ElseIf keyState.right Then
    heroDir = 2
ElseIf keyState.down Then
    heroDir = 4
ElseIf keyState.left Then
    heroDir = 6
Else
    heroDir = -1
End If

REM get the untimed core frame rate
Dim frameRate As Integer = game.FrameRate()
REM drawing code should be limited to 60 fps
Dim ticks As Integer = Environment.TickCount()
Static drawLast As Integer = 0
If ticks > drawLast + 16 Then '1000/16 = ~60
    drawLast = ticks
    REM draw the tilemap
    level.Draw(0, 0, 800, 600)
    REM draw the hero
    Dim startFrame As Integer = heroDir * 9
    Dim endFrame As Integer = startFrame + 8
    If heroDir > -1 Then
        hero.Animate(startFrame, endFrame)
    End If
    hero.Draw()
    REM print stats
    game.Print(700, 0, frameRate.ToString())
    Dim y As Integer = 0
    game.Print(0, y, "Scroll " + level.ScrollPos.ToString())
    y += 20
    game.Print(0, y, "Player " + hero.Position.ToString())
    y += 20
    Dim feet As Point = HeroFeet()

        Dim tilex As Integer = (level.ScrollPos.X + feet.X) / 32
        Dim tiley As Integer = (level.ScrollPos.Y + feet.Y) / 32
        Dim ts As Level.tilemapStruct
        ts = level.getTile(tilex, tiley)
        game.Print(0, y, "Tile " + tilex.ToString() + "," + _
            tiley.ToString() + " = " + ts.tilenum.ToString())
        y += 20
        If ts.collidable Then
            game.Print(0, y, "Collidable")
            y += 20
        End If
        If ts.portal Then
            game.Print(0, y, "Portal to " + ts.portalx.ToString() + _
                "," + ts.portaly.ToString())
            portalFlag = True
            portalTarget = New Point(ts.portalx - feet.X / 32, _
                ts.portaly - feet.Y / 32)
            y += 20
        Else
            portalFlag = False
        End If

        REM highlight collision areas around player
        game.Device.DrawRectangle(Pens.Blue, hero.Bounds())
        game.Device.DrawRectangle(Pens.Red, feet.X + 16 - 1, _
            feet.Y + 16 - 1, 2, 2)
        game.Device.DrawRectangle(Pens.Red, feet.X, feet.Y, 32, 32)

        REM refresh window
        game.Update()
        Application.DoEvents()
    Else
        REM throttle the cpu
        Threading.Thread.Sleep(1)
    End If
End Sub

REM return bottom center position of hero sprite where feet are touching
    ground
Private Function HeroFeet() As Point

        Return New Point(hero.X + 32, hero.Y + 32 + 16)
    End Function
End Class

Adding Lua Scripting Support to the Game

When you combine the versatility that a data-driven game engine affords, along with a custom level editor, you already have a great combination for making a great game. But when you add script support to the mix, things get even more interesting! We have progressed to the point in both the game and the level editor where, sure, we could get by with the excellent tools and code already in hand, but I want to raise the cool factor even higher with the addition of scripting support.

Now, let me disclaim something first: Yes, scripting is cool and adds incredible power to a game project, but it requires a lot of extra effort to make it work effectively.

The cool factor is that we can call Basic functions from within our Lua scripts! Likewise, we can call Lua functions from our Basic code—interpreted Lua functions!

But what about all of the global variables in a Lua source code file? The variables are automatically handled by the Lua engine when a script file is loaded. I’m not going to delve into a full-blown tutorial on the Lua language. Instead, we’re just going to use it and learn more about Lua as needed. After this brief introduction, we will not be using Lua until the final game in Chapter 20. If you’re really fascinated about Lua and want to dig into scripting right away, you may skip ahead to that chapter to see how it works in the finished game engine.

Hint

There is one really big drawback to Lua: once you have “seen the light,” you may never go back to writing a game purely with a compiled language like Basic or C# again! Lua is so compelling that you’ll wonder how in the world you ever got anything done before you discovered it.

Installing LuaInterface

The key to adding Lua support to our Visual Basic code is an open-source project called LuaInterface, hosted at the LuaForge website here: http://luaforge.net/projects/luainterface/. The sources for LuaInterface are housed in a Google Code Subversion (SVN) repository at http://code.google.com/p/luainterface/, currently with support for Visual Basic 2008. I have included a project with this chapter that has the pre-compiled version of LuaInterface ready to use.

Definition

Lua is the Portuguese word for “Moon.” The official spelling is LUA, with all caps, but I prefer to spell it without all caps because that leads the reader to assume it’s an acronym rather than a word.

Testing LuaInterface

After compiling the LuaInterface project, you’ll get a file called LuaInterface. dll—that’s all you need. Copy this file to any project that needs Lua support and you’ll be all set. (Note also that this dll must be distributed with your game’s exe file.) Whether you compiled it yourself or just copied it from the chapter resource files, create a new Visual Basic project. Then, open the Project menu and select Add Reference. Locate the LuaInterface.dll file and select it, as shown in Figure 13.8.

Adding the LuaInterface.dll file to the project.

Figure 13.8. Adding the LuaInterface.dll file to the project.

Nothing will seem to change in the project. To verify that the component has been added, open Project, Properties, and bring up the References tab, where you should see the component among the others available to your project. See Figure 13.9.

List of referenced components available for this project.

Figure 13.9. List of referenced components available for this project.

Double click the default form to bring up the code window. At the top, type Imports. When you hit space, a list of namespaces should appear, including LuaInterface. If you don’t see LuaInterface among those listed, then something went wrong when the component was added to the project as a reference, and you’ll need to figure that out first before proceeding.

Hint

If you get an error such as this one below when running the Lua Script Demo program, that simply means that the program could not find the lua51.dll file, which is required in order for LuaInterface. dll to work properly. Be sure both dll files are located in the .inDebug folder of your project.

An unhandled exception of type’ System.InvalidOperationException’ occurred in Lua Script Demo. exe. Additional information: An error occurred creating the form. See Exception.InnerException for details. The error is: Could not load file or assembly’ lua51, Version=0.0.0.0, Culture=neutral, PublicKeyToken=1e1fb15b02227b8a’ or one of its dependencies. The system cannot find the file specified.

Here is our first short example Basic program that loads a Lua script file. The form for this program has a TextBox control, which is used as a simple console for printing out text from both the Lua script and our Basic code. Figure 13.10 shows the result of the program.

We now have Lua script language support for our game.

Figure 13.10. We now have Lua script language support for our game.

Imports LuaInterface
Public Class Form1
    Private WithEvents TextBox1 As New TextBox()
    Public lua As Lua
    Private Sub Form1_Load(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles MyBase.Load
        Me.Text = "Lua Script Demo"
        TextBox1.Dock = DockStyle.Fill
        TextBox1.Multiline = True
        TextBox1.Font = New Font("System", 14, FontStyle.Regular)
        Me.Controls.Add(TextBox1)

        REM create lua object
        lua = New Lua()

        REM link a Basic function to Lua
        lua.RegisterFunction("DoPrint", Me, Me.GetType().GetMethod
          ("DoPrint"))

        REM load lua script file
        lua.DoFile("script.lua")

        REM get globals from lua
        Dim name As String = lua("name")
        Dim age As Double = lua("age")
        DoPrint("name = " + name)
        DoPrint("age = " + age.ToString())
    End Sub

    Public Sub DoPrint(ByVal text As String)
        TextBox1.Text += text + vbCrLf
    End Sub
End Class

Hint

The LuaInterface.dll requires the .NET Framework 2.0, not the later versions such as 3.5. If you are using Visual Basic 2010, it will default to the later version of the .NET Framework. To get LuaInterface to work with your Visual Basic 2010 project, you will need to switch to .NET 2.0. If this is too difficult to figure out, then another option is to just create a new Visual Basic 2010 project and copy the source code into it rather than converting the 2008 project that comes with the book to 2010.

First, the TextBox control is created and added to the form with the Multiline property set to true so the control acts like a console rather than an entry field.

Next, the LuaInterface.Lua object is created. That object, called lua, is then used to register a Basic function called DoPrint(), which is defined as a normal Basic Sub (note that it must be declared with Public scope in order for Lua to see it!). Next, lua.DoFile() is called to load the script code in the script.lua file. This file must be located in the .inDebug folder where the executable file is created at compile time. So, we can think of a script file like any game asset file, equivalent to a bitmap file or an audio file.

When DoFile() is called, that not only opens the script file, it also executes the code. This is one of the two ways to open a script file. The second way is to use LoadFile() instead, which simply loads the script into memory, registers the functions and globals, but does not start executing statements yet.

After the script has been loaded and run, then we can tap into the lua object to retrieve globals from the lua object, as well as call functions in the script code. In this example, we just grab two globals (name and age) and print out their values. This demonstrates that Lua can see our Basic function and call it, and that we can tap into the globals, which is the most important thing!

Here is the script.lua file for this project:

--This is my first Lua Script!
--create some globals
name = "John"
age = 20
--call a function in the Basic code
DoPrint( "Welcome to " .. _VERSION )

Hint

Do you see that odd-looking pair of dots in the last line of the script file? The double dot is Lua’s way of attaching strings together (while Basic and most other languages use the plus (+) operator).

Sharing Tile Data with Lua

What’s next? We have a Lua linkage in our Basic project, so we should give Lua more control over our game data. I want to be able to scroll the game world to any location with a function call, as well as read the data for any tile on the tilemap, including the tile under the player’s current position, for obvious reasons. Once those things are done, then it will be possible to add Lua functions to the tilemap via our level editor. At that point, the game engine becomes less of a factor for gameplay code. Any variable in the Basic program can be sent to the Lua code as a global, and vice versa! This level of cooperation along with the runtime interpretation of Lua script makes it an extremely valuable addition to our project. We will use these advanced features of Lua in the final game project in Chapter 20!

Level Up!

This chapter saw some dramatic improvements to both the level editor and the Celtic Crusader game code, with the addition of code to detect collidable tiles, and code to make portals active, allowing us to teleport the player to a new location. Although the level editor provides the “portalfile” field to enable teleporting to a position in a different level file, we will reserve that feature for later. Finally, by adding Lua Scripting support to the engine we will be able to do some remarkable things in script code that will free up some of the requirements on our Basic program.

Believe it or not, we now have a game world that is suitable as an environment for the Celtic Crusader game! That means we can shift focus from the game world and level editing over to a new subject—people and monsters!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.123.227