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:
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)); ... }
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;
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); .... }
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); }
[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
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.