One of the most interesting thing when you develop a game is to test each feature easily. It’s not possible for a developper to walk through a level to actualy check if cinematic trigger is working or to check if AI of a boss is normal.
A developper wants to activate easily the ‘godmod’ for example, that’s why it’s necessary to create a Console that will make the developper’s life better.
The Shell
The shell is the core of your console. The shell should allow developper to register commands, to execute a command, to get the syntax and to get the list of registered commands.
Dictionnaries are not the most optimized collection object but they are really interesting to use. I have different way to manipulate object when I use different language but the global idea don’t change.
For example in C# I’m a fond of using structure but it’s not existing in ActionScript and I prefere avoid to use internal class. That’s why I’ll use 2 disctionnary in AS3 and only one in C#.
Action Script 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | package multiFreid.core { import flash.utils.Dictionary; import multiFreid.error.ConsoleError; /** * The shell will centralize all command that developpers need to create for a project * @version 1.0 * @author multifred */ public class Shell { protected var commands:Dictionary; protected var manuals:Dictionary; protected static var instance:Shell; public function Shell() { commands = new Dictionary(); manuals = new Dictionary(); instance = this; } /** * Register a new command * @param cmd - the command * @param callback - the callback function * @param manual - The details about the command * @throws ConsoleError - Throws if the cmd is already set in the command list */ static public function RegisterCmd(cmd:String, callback:Function, manual:String = "42"):void { if (instance.commands[cmd]) { throw ConsoleError.DUPLICATE_CMD; } else { instance.commands[cmd] = callback; instance.manuals[cmd] = manual; } } /** * Execute the command * @param cmd * @param args */ public function execute(cmd:String, args:Array):void { if (commands[cmd]!=null) { var f:Function = commands[cmd] as Function; f(args); } else { throw ConsoleError.UNKNOWN_CMD; } } /** * Get all commands * @return the list of registered commands */ public function getCommands():Array { var arr:Array = new Array(); for ( var cmd:String in commands) { arr.push(cmd); } return arr; } /** * get the manual the specified command * @param cmd the command * @return the manual */ public function getManual(cmd:String):String { if (manuals[cmd] != null) { return manuals[cmd]; } else { throw ConsoleError.UNKNOWN_CMD; } } } } |
To register your commands the syntaxe will be simple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | protected function InitDefaultCmd():void { Shell.RegisterCmd( "list", cmdDisplayList, "List all available commands"); Shell.RegisterCmd( "cls", cmdClear, "clear the screen"); Shell.RegisterCmd( "man", cmdDisplaySyntaxe, "man <cmd> display the manual of the specified command <cmd>"); Shell.RegisterCmd( "exit", cmdExit, "Close the console without prompt"); } |
C#
In C# you cannot use a function in an argument as easily as it is in ActionScript. You’ll need to use delegate:
1 | public delegate <Treturn> MyDelegate(<T0> args, ...); |
Delegate is be a powerful tool, use it !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | using UnityEngine; using System.Collections; using System; namespace Multifred.System { public class Shell { #region Delegate /// <summary> /// CMD callback - The Delegate. /// </summary> public delegate void CMDCallback(ArrayList args); #endregion #region init static protected Shell _instance; static public Shell instance { get{ if(_instance==null) { _instance = new Shell(); } return _instance; } } protected struct Entry { public string cmd; public CMDCallback callback; public string manual; }; protected Hashtable entries; /// <summary> /// Initializes a new instance of the <see cref="Shell"/> class. /// </summary> public Shell() { entries = new Hashtable(); } #endregion #region entry commands static protected void RegisterCommand(Entry cmd) { instance.entries.Add(cmd.cmd, cmd); } static public void RegisterCommand(string cmd, CMDCallback callback) { RegisterCommand(cmd, callback, "No information"); } /// <summary> /// Registers a new command. /// </summary> /// <param name='cmd'> /// Cmd - the command the developper wish to register /// </param> /// <param name='functionName'> /// Function name - the callback function /// </param> /// <param name='manual'> /// Manual. /// </param> /// <exception cref=""> /// Throw an exception if the specified cmd is already registered /// </exception> static public void RegisterCommand(string cmd, CMDCallback callback, string manual) { Entry command; if(instance.entries.Contains(cmd)) { throw new Exception("The command '"+cmd+"' already exist."); } command.cmd = cmd; command.callback = callback; command.manual = manual; RegisterCommand(command); } /// <summary> /// Executes the callback of the specified command with the specified args /// </summary> /// <param name='cmd'> /// cmd - the command the user used /// </param> /// <param name='args'> /// args - the arguments for the cmd /// </param> /// <returns> /// <c>true</c> if the cmd is found. /// </returns> public bool Execute(string cmd, ArrayList args) { if(entries.Contains(cmd)) { Entry val= (Entry)entries[cmd]; val.callback(args); return true; } return false; } #endregion #region default commands /// <summary> /// Do nothing. /// </summary> /// <param name='args'> /// Arguments - useless args /// </param> private void doNothing(ArrayList args) { return; } public string getManual(string cmd) { if(entries.Contains(cmd)) { Entry e = (Entry)(entries[cmd]); return e.manual; } return null; } public ArrayList getCommands() { ArrayList arr =new ArrayList(); foreach(DictionaryEntry entry in entries) { arr.Add(entry.Key); } return arr; } #endregion } } |
And here how use it :
1 2 3 4 5 6 7 8 9 10 11 12 | Shell.RegisterCommand( "man", new Shell.CMDCallback(callbackManual), "Display the manual of the command\n\tSyntax:\t\tman <cmd>"); Shell.RegisterCommand( "cls", new Shell.CMDCallback(callbackCLS), "Clear the debug console's screen"); Shell.RegisterCommand( "list", new Shell.CMDCallback(callbackList), "Display the list of all register function"); |
An interface now ?
Now that you have a working shell you will need to create a Command-Line Interface (CLI). The CLI is a display object so the way it work is completely different because of the framework you use to develop.
In the second part of this tuto I’ll create a CLI for Flash and for Unity and you will see big differences !