Debug Console for Flash and Unity – Part 1

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 !