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