Game Development on a New Frontier RSS 2.0
 Sunday, March 09, 2008

I’ve made an extension to the Console component sample posted on Ziggyware.  It provides access to a Lua virtual machine through the console.  I believe many of you budding (or experienced) XNA developers out there may find of use. 

I’d like to thank Kevin Jurkowski for his Console sample, it gave me a fine starting point from which to expand upon.  Also, I’d like to note that I have updated his sample to run on XNA 2.0.  Second, I’d like to thank Martin Echenique’s great example of adding Lua functions to a .NET program using reflection, as detailed here.  I wrote about this project in a previous post and expanded upon it somewhat; this is the base Lua project that I utilize for this sample.  In this post, I will simply explain how I integrated it into the Console component.

Download my sample project here:

ConsoleWithLua.zip (801.74 KB)


First, I added a Lua function, GetFPS, to the Game1 class.  This returns the FPS component added to our Game class.  Make sure to include the LuaNetInterface assembly.

using LuaNetInterface;

/// <summary>
/// Gets the FPS component of the game.
/// </summary>
[LuaFunctionAttribute("GetFPS", "Gets the FPS component.")]
public DrawableGameComponent GetFPSComponent()
{
return FPS;
}

Then, I added a Lua virtual machine to the Game1 class.  After initializing it, I registered the functions in this class (the only function that gets registered is our GetFPS() method).  When we create the ConsoleComponent object, we pass our Lua virtual machine as an argument.

/// <summary>
/// The Lua virtual machine for our game.
/// </summary>
private LuaVirtualMachine lua;

public Game1()
{
    ...

    // Create our Lua virtual machine
    lua = new LuaVirtualMachine();
    lua.RegisterLuaFunctions(this);
    Components.Add(new ConsoleComponent(this, lua));

    ...
}
These are all of our changes to the main Game1 class.  I also added a basic InputManager class, but it’s nothing out of the usual for usage of keyboard input.  Just take a quick look at the class if you need to; it’s fairly basic and self-explanatory. 

On to the modified ConsoleComponent class.  I added the LuaInterface and LuaNetInterface assemblies to the class to access the needed Lua objects.

using LuaInterface;
using LuaNetInterface;
We added a LuaVirtualMachine (Lua VM) object to the console class.  It stores the Lua VM object sent in through the object’s constructor.  In the Initialize() method, the Lua VM registers the functions in our class (note: we’ll add these later).  I also created an instance of our input manager class in the console class.

private LuaVirtualMachine _lua;
private InputManager _inputManager;

public ConsoleComponent(Game game, LuaVirtualMachine lua)
        : base(game)
{
    // If no Lua scripting virtual machine was passed in, create one
    if (lua == null)
        lua = new LuaVirtualMachine();

    _lua = lua;
}

public override void Initialize()
{
    ...

    _inputManager = new InputManager(Game);

    ....
}
I added a ValidCharacter struct to the console class.  It specifies valid characters that can be entered (both normal characters and alternate characters that are displayed when a Shift key is pressed).  I defined the list of valid characters in the RegisterValidKeys() function, called from within Initialize().  These are held within a hash table for quick access.

internal struct ValidCharacter
{
    /// <summary>
    /// The standard character to display.
    /// </summary>
    public char Character;

    /// <summary>
    /// The character to display when the Shift key is pressed.
    /// </summary>
    public char ShiftedCharacter;

    /// <summary>
    /// Creates a valid text characters structure.
    /// </summary>
    /// <param name="ch">The standard character to display.</param>
    /// <param name="shiftCh">The character to display when the Shift key is pressed.</param>
    public ValidCharacter(char ch, char shiftCh)
    {
        Character = ch;
        ShiftedCharacter = shiftCh;
    }
}

I radically re-vamped the Update() method.  First, I go through all of the pressed keys from the input manager and update our command string based upon the valid keys pressed that we have registered.

bool shiftDown = _inputManager.CurrentKeyboardState.IsKeyDown(Keys.LeftShift) ||
    _inputManager.PreviousKeyboardState.IsKeyDown(Keys.RightShift);
Keys[] keysPressed = _inputManager.CurrentKeyboardState.GetPressedKeys();

// No need to do further processing if no keys were pressed
if (keysPressed.Length > 0)
{
    // Process valid keys just pressed
    foreach (Keys key in keysPressed)
    {
        if (_validChars.ContainsKey(key) &&
            _inputManager.PreviousKeyboardState.IsKeyUp(key))
        {
            ValidCharacter ch = (ValidCharacter)_validChars[key];

            if (shiftDown)
                _command += ch.ShiftedCharacter;
            else
                _command += ch.Character;
        }
    }
}

I also added some functionality for scrolling up and down through our console log.  This is not critical to our changes, so I won’t discuss it in-depth, but you may find it useful to look over.  These update changes modify our Draw() function a bit, but nothing serious.  The meat of the final changes to Update() are where we enter a valid command and process it using the Lua VM.  From there on, Lua handles the rest!

if (_inputManager.JustPressed(Keys.Enter) &&
    _command != "")
{
    _logOffset = 0;

    _log.Add("");
    _log.Add(_prefix + _command);

    try
    {
        if (_lua != null)
        {
            if (_lua.IsRunning)
                _lua.Lua.DoString(_command);
            else
                _log.Add("ERROR: Lua Virtual Machine is no longer running.");
        }
        else
            _log.Add("ERROR: Lua Virtual Machine has not been set.");
    }
    catch (LuaException e)
    {
        _log.Add("Lua Error: " + e.Message);
    }

    _command = "";
}

I also updated the Draw() method a bit.  I’ll reproduce it here, but there should be nothing to surprise you there, outside of the inconsequential display changes I mentioned earlier.

public override void Draw(GameTime gameTime)
{
    if (_active == true)
    {
        float prefixHeight = _font.MeasureString(_prefix).Y;
        float drawHeight;

        // Saves a copy of the current screen into the texture.  This allows the menus to be transparent
        Game.GraphicsDevice.ResolveBackBuffer(_texture);

        // Without restoring the states properly, the alpha blending will cause some problems
        _spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

        // Draw the full background texture
        _spriteBatch.Draw(_texture,
            new Rectangle(0, 0, Game.GraphicsDevice.Viewport.Width, Game.GraphicsDevice.Viewport.Height),
            Color.White);

        // Draw transparent menu
        _spriteBatch.Draw(_texture,
            new Rectangle(0, 0, _width, _height),
            new Rectangle(0, 0, _width, _height),
            Color.Gray);

        // Draw command string
        _spriteBatch.DrawString(_font,
            _line + _prefix + _command,
            new Vector2(10f, _height - (prefixHeight * 2f) - 4f),
            Color.White);

        // Draw log
        for (int i = 0; i < _log.Count; i++)
        {
            drawHeight = _height - 34 + (prefixHeight * i) - (prefixHeight * _log.Count) +
                (_logOffset * prefixHeight);

            if (drawHeight + prefixHeight > 0 && drawHeight < _validDisplayHeight * prefixHeight)
            {
                _spriteBatch.DrawString(_font,
                    _log[i],
                    new Vector2(10f, drawHeight),
                    Color.Silver);
            }
        }

        _spriteBatch.End();
    }

    base.Draw(gameTime);
}
Lastly, we add our Lua methods to the console class.  Most of these are basic helper functions (help() displays a list of available functions, helpcmd() gets you an in-depth description of an individual function, and print() simply reprints the line you entered).  The most notable function here is the ToggleComponent() method.  This enables or disables whatever component you pass to the function.  Within the Lua commands, this is accessed as “Toggle”.

[LuaFunctionAttribute("Toggle", "Turns a component on or off.",
   "The component to toggle on or off.")]
public void ToggleComponent(DrawableGameComponent component)
{
    component.Enabled = !component.Enabled;
    component.Visible = !component.Visible;
}

This constitutes all of the major changes to the sample.  Now on to the fun part!  Let’s use our magic!

Run the program.  You should see the FPS component displayed in the bottom left hand corner.  Press the tilde (~) key to open up the console.  Type “help()” to get a list of available commands if you wish.  Enter the following commands into the console:

fps = GetFPS()
Toggle(fps)

At this point, you should see the FPS component disappear.  If you want to re-enable it, re-enter the “Toggle(fps)” command.

I hope this gives you a start in easily adding Lua into your XNA programs and gives you an easy interface into accessing the Lua VM!


Cheers!

Devan Stormont

Sunday, March 09, 2008 4:52:01 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
.NET | XNA
Categories
 
 
 
 
 
 
Archive
<March 2008>
SunMonTueWedThuFriSat
2425262728291
2345678
9101112131415
16171819202122
23242526272829
303112345
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2009
Devan Stormont
Sign In
Statistics
Total Posts: 10
This Year: 0
This Month: 0
This Week: 0
Comments: 0
Themes
Pick a theme:
All Content © 2009, Devan Stormont
DasBlog theme 'Business' created by Christoph De Baene (delarou)