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:
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.
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.
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
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).
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.
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.
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.
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
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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
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 )
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!
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!
18.218.97.75