我们在开发游戏的过程中,通常都需要一些快捷的方式来进行一些非常规的测试,这些功能一般被称作控制台或者GM指令,比如虚幻竞技场中,可以使用~键呼出控制台,输入一些指令即可进行快捷设置,比如设置分辨率,全屏与否,添加机器人等等,这些功能在正常流程中通常来说需要单独开发游戏界面来实现。然而很多时候我们并不想开放这些功能,比如需要一个让自己无敌的命令。

虚幻竞技场中的ConsoleCommand

我开发这个小插件的原因就是让你可以用最简单的方式来实现这些功能。项目GitHub地址:https://github.com/sczybt/CheatConsole 。 可以使用git clone代码或者直接下载最新版本的zip包。支持Unity4.0.0以及之后的所有版本unity,包括unity5.x。

主要的功能除了提供与虚幻类似的PC版本使用的命令行控制台之外,还提供了手机使用的移动模式,当然还有方便开发人员使用的编辑器模式,这些都不需要开发者手动的去设计或编辑,只需要按照要求写代码即可,所有的功能将会通过反射自动实现。

编辑器模式下的Cheat功能

编辑器模式下的Cheat功能

移动模式,所有操作通过点击按钮即可完成

所有模式的数据都是共享的

PC的控制台模式 提供智能搜索,自动完成,历史记忆,参数正确性智能验证等功能

错误的参数被自动识别并提供错误信息

我制作了一个开发指南视频放在了youtube【需FQ】上,用于展示所有功能和开发方式。并且在youtube上有专门的注释进行说明

腾讯视频。没有文字注释与描述。

开发指南

整套系统的入口在类ConsoleWindow中,要开启系统,需要在恰当的地方进行初始化,示例初始化代码:

 namespace Assets.Scripts.Framework
 {
     public class GameFramework :
         MonoSingleton<GameFramework>
     {
         protected override void Init()
         {
             base.Init();

 #if !WITH_OUT_CHEAT_CONSOLE
             ConsoleWindow.instance.isVisible = false;

             // you can set this flag by the networking message
             // eg:is this account a gm account?
             ConsoleWindow.instance.bEnableUseGmPanel = true;

             CheatCommandRegister.instance.Register(typeof(GameFramework).Assembly);
 #endif
         }
     }
 }

这里有三个步骤,1.GetInstance,这样会创建对应的实例。2.设置是否允许Panel存在,这个可以根据账号信息来设置。3.注册指令代码所在的Assembly(程序集),这样系统将自动找到该程序集中的指令代码并注册给系统。所有指令都存在在类CheatCommandsRepository中。

整套系统可以通过定义宏WITH_OUT_CHEAT_CONSOLE来完全屏蔽掉代码,因此强烈建议与之相关的代码都通过宏#if !WITH_OUT_CHEAT_CONSOLE包含起来,这样如果确定了发布版本中不需要这些代码的时候,可以很简单的一次性移除。

要新增一个Cheat指令非常简单,目前有两种方式,其一是框架式,其二是函数式。前者相对复杂一点,但是功能更强大,扩展性也更高,适用于复杂指令或者需要与服务器同步的指令;后者更简单,适用于本地指令。

     enum PlayerAttrType
     {
         Health,
         MaxHealth,
         Exp,
         MaxExp,
         Level
     }

     [CheatCommandAttribute("Player/Client/SetPlayerAttr", "Change Player Attribute Value.")]
     [ArgumentDescriptionAttribute(, typeof(PlayerAttrType), "Attribute Type")]
     [ArgumentDescriptionAttribute(ArgumentDescriptionAttribute.EDefaultValueTag.Tag, , ")]
     public class SetPlayerAttrCommand : CheatCommandCommon
     {
         protected override string Execute(string[] InArguments)
         {
             if( ConsoleWindow.HasInstance() )
             {
                 PlayerAttrType Type = (PlayerAttrType)StringToEnum(InArguments[], typeof(PlayerAttrType));
                 ]);

                 ConsoleWindow.instance.AddMessage(
                     string.Format("Set Player {0} to {1}", Type.ToString(), Value)
                     );

                 return Done;
             }

             return "No Console Window";
         }
     }

首先展示的是一个比较简单的指令,这个指令可以用于设置玩家的某些属性。开头的Attribute指出了这个指令的一些基本信息。Player/Client/SetPlayerAttr代表的是分页信息与指令控制台名称,Change Player Attribute Value.为要显示的命令名称。后面两个参数描述Attribute对指令的参数进行了描述,这样在实际使用过程中,会提前对参数有效性进行检查。

代码对应的指令【移动模式】

代码对应的指令【控制台模式】

     public enum TestEnum
     {
         AAA,
         BBB,
         CCC
     }

     [CheatCommandAttribute("Test/TestNormal", "DemoTestNormal")]
     [ArgumentDescriptionAttribute(, typeof(TestEnum), "Attr")]
     [ArgumentDescriptionAttribute(, , "AAA|CCC")]
     [ArgumentDescriptionAttribute(, , "AAA|CCC")]
     public class TestCommandNormal : CheatCommandCommon
     {
         protected override string Execute(string[] InArguments)
         {
             TestEnum EnumValue = (TestEnum)StringToEnum(InArguments[], typeof(TestEnum));

             ConsoleWindow.instance.AddMessage(EnumValue.ToString());

             return Done;
         }
     }

这是另外一个稍微麻烦一点的指令,与之前的相比,它多出了参数依赖的描述。参数1和参数2都依赖于参数0,描述的含义即当参数1是AAA或者CCC时,参数1和参数2才需要,否则不需要提供参数1和参数2。

可选参数的设计

这套方案提供了另外一个NetworkingChectCommand的基类,可以方便实现需要联网的指令,当然这并非必须的。基于这套框架,你可以轻松自己设计用于网络的指令系统。

除了框架式之外,还有函数式可以选择,函数式更加简单便捷。

     [CheatCommandEntryAttribute("Test")]
     public class TestCommandEntry
     {
         [CheatCommandEntryMethodAttribute("TestEntry1", false)]
         public static string TestEntry1( string InText )
         {
             ConsoleWindow.instance.AddMessage(InText);

             return CheatCommandBase.Done;
         }

         [CheatCommandEntryMethodAttribute("Tests/TestEntry2", false)]
         public static string TestEntry2( TestEnum InEnum )
         {
             ConsoleWindow.instance.AddMessage(((int)InEnum).ToString());

             return CheatCommandBase.Done;
         }
     }

首先需要为要写指令的类添加一个CheatCommandEntryAttribute的Attribute,为指令的声明函数添加CheatCommandEntryMethodAttribute的Attribute。指令函数必须是静态函数,返回值必须是string,参数只能是内置类型或枚举。当然,通过实现接口IArgumentDescription就可以支持更多的自定义类型参数,其中Enum类型的参数即是通过这个方法来实现支持的。

)]
     public class ArgumentDescriptionEnum : IArgumentDescription
     {
         public bool Accept(Type InType)
         {
             return InType != null && InType.IsEnum;
         }

         public string GetValue(Type InType, string InArgument)
         {
             DebugHelper.Assert(InArgument != null);

             string[] Enums = Enum.GetNames(InType);            

             ; i<Enums.Length; ++i)
             {
                 if( Enums[i].Equals(InArgument, StringComparison.CurrentCultureIgnoreCase) )
                 {
                     return Enums[i];
                 }
             }

             string DummyString;

             if (ArgumentDescriptionDefault.CheckConvertUtil(InArgument, typeof(int), out DummyString))
             {
                 int EnumValue = System.Convert.ToInt32(InArgument);

                 string Result = Enum.GetName(InType, EnumValue);

                 return Result;
             }

             return "";
         }

         public bool CheckConvert(string InArgument, Type InType, out string OutErrorMessage)
         {
             DebugHelper.Assert(InArgument != null && InType.IsEnum);

             OutErrorMessage = "";

             string[] Enums = Enum.GetNames(InType);

             ; i < Enums.Length; ++i)
             {
                 if (Enums[i].Equals(InArgument, StringComparison.CurrentCultureIgnoreCase))
                 {
                     return true;
                 }
             }

             string DummyString;

             if (ArgumentDescriptionDefault.CheckConvertUtil(InArgument, typeof(int), out DummyString))
             {
                 int EnumValue = System.Convert.ToInt32(InArgument);

                 string Result = Enum.GetName(InType, EnumValue);

                 if( string.IsNullOrEmpty(Result) )
                 {
                 OutErrorMessage = string.Format("Failed Convert\"{0}\" to {1}.", InArgument, InType.Name);
                 }

                 return false;
             }

             OutErrorMessage = string.Format("Value \"{0}\" is not an valid property.", InArgument);

             return false;
         }

         public static int StringToEnum(Type InType, string InText)
         {
             // assume this input text always can be convert to enum.
             string DummyString;
             if (ArgumentDescriptionDefault.CheckConvertUtil(InText, typeof(int), out DummyString))
             {
                 int EnumValue = System.Convert.ToInt32(InText);

                 return EnumValue;
             }
             else
             {
                 return System.Convert.ToInt32(Enum.Parse(InType, InText, true));
             }
         }

         public List<string> GetCandinates(Type InType)
         {
             string[] Results = Enum.GetNames(InType);

             return Results != null ? LinqS.ToStringList(Results) : null;
         }

         public List<string> FilteredCandinates(Type InType, string InArgument)
         {
             string DummyString;
             if (ArgumentDescriptionDefault.CheckConvertUtil(InArgument, typeof(int), out DummyString))
             {
                 int EnumValue = System.Convert.ToInt32(InArgument);
                 string CurrentString = Enum.GetName(InType, EnumValue);

                 return FilteredCandinatesInner(InType, CurrentString);
             }
             else
             {
                 return FilteredCandinatesInner(InType, InArgument);
             }
         }

         protected List<string> FilteredCandinatesInner(Type InType, string InArgument)
         {
             List<string> Results = GetCandinates(InType);

             if( Results != null && InArgument != null )
             {
                 Results.RemoveAll((x) => !x.StartsWith(InArgument, StringComparison.CurrentCultureIgnoreCase));
             }

             return Results;
         }

         public bool AcceptAsMethodParameter(Type InType)
         {
             return InType.IsEnum;
         }

         public object Convert(string InArgument, Type InType)
         {
             string DummyString;
             if (ArgumentDescriptionDefault.CheckConvertUtil(InArgument, typeof(int), out DummyString))
             {
                 int EnumValue = System.Convert.ToInt32(InArgument);

                 return Enum.ToObject(InType, EnumValue);
             }
             else
             {
                 return Enum.Parse(InType, InArgument, true);
             }
         }
     }

默认的操作:

F1:打开PC控制台模式

Ctrl + F1:打开PC移动模式

编辑器Tools/CheatPanel:打开编辑器模式

Esc:退出

Tab:自动完成

上下箭头:控制台模式中,候选项中上下选择

Ctrl+上下箭头:控制台模式中,历史记录中选择

在手机上:五指以上的手指同时触摸屏幕可以开关界面

开源Unity小插件CheatConsole的更多相关文章

  1. Creator开源游戏、插件、教程、视频汇总

    Creator开源游戏.插件.教程.视频汇总 来源 http://forum.cocos.com/t/creator/44782 王哲首席客服   17-03-17    4   史上最全,没有之一. ...

  2. 开发unity DLL插件

    最近开发一款设备的SDK,想着要开发unity版本,怎么做呢?首先想到的就是在外部编写相关的驱动程序然后集成成几个dll作为unity的SDK使用了.So,我就开始了unity外部插件的研究之旅了. ...

  3. 浮动【电梯】或【回到顶部】小插件:iElevator.js

    iElevator.js 是一个jquery小插件,使用简单,兼容IE6,支持UMD和3种配置方式,比锚点更灵活. Default Options _defaults = { floors: null ...

  4. 自制Unity小游戏TankHero-2D(5)声音+爆炸+场景切换+武器弹药

    自制Unity小游戏TankHero-2D(5)声音+爆炸+场景切换+武器弹药 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm ...

  5. 自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

    自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm ...

  6. 自制Unity小游戏TankHero-2D(3)开始玩起来

    自制Unity小游戏TankHero-2D(3)开始玩起来 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的.仅 ...

  7. 自制Unity小游戏TankHero-2D(2)制作敌方坦克

    自制Unity小游戏TankHero-2D(2)制作敌方坦克 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的. ...

  8. 自制Unity小游戏TankHero-2D(1)制作主角坦克

    自制Unity小游戏TankHero-2D(1)制作主角坦克 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的. ...

  9. aBowman >>可以运用到自己博客上的小插件

    大家进入我的博客会发现页面右边有一只小狗这部分.这个就是我用在上面的 一个小插件.插件网址是:http://abowman.com/google-modules/,这上面有很多的小插件,可以直接运用到 ...

随机推荐

  1. 【先定一个小目标】怎么解决mysql不允许远程连接的错误

    最近使用Navicat for MySQl访问远程mysql数据库,出现报错,显示“1130 - Host'xxx.xxx.xxx.xxx' is not allowed to connect to ...

  2. #pragma once与#ifndef #define ...#endif的区别

    1. #pragma once用来防止某个头文件被多次include: #ifndef,#define,#endif用来防止某个宏被多次定义.   2. #pragma once是编译相关,就是说这个 ...

  3. springmvc 文件上传实现(不是服务器的)

    1.spring使用了apache-commons下的上传组件,因此,我们需要引用2个jar包 1)apache-commons-fileupload.jar 2 ) apache-commons-i ...

  4. Github .gitignore详解

    在使用git作版本控制时,git会默认把git控制的文件夹里面的所有文件都加入到版本控制.但是在实践中,我们经常会遇到不想某些文件或文件夹被git追踪的情况.比如logs文件.代码构建过程中产生的一些 ...

  5. PL/SQL存储过程编程

    PL/SQL存储过程编程 /**author huangchaobiao *Email:huangchaobiao111@163.com */ PL/SQL存储过程编程(上) 1. Oracle应用编 ...

  6. jQuery所支持的css样式

    jQuery所支持的css样式 backgroundPosition borderWidth borderBottomWidth borderLeftWidth borderRightWidth bo ...

  7. struts.xml

    <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE struts PUBLIC    " ...

  8. java初始化

    一.成员初始化 1.成员变量没有赋值,则被初始化成默认值. 2.局部变量没有赋值,编译时报错. 二.构造器初始化 1.成员变量在构造器初始化之前,已经被初始化. 2.变量定义的顺序决定了初始化的顺序. ...

  9. Gogland 个性化设置

    1.去掉 hints 提示功能: Preferences -> Editor -> General -> Appearance -> 去掉勾选 “Show parameter ...

  10. DataTable to Excel(使用NPOI、EPPlus将数据表中的数据读取到excel格式内存中)

    /// <summary> /// DataTable to Excel(将数据表中的数据读取到excel格式内存中) /// </summary> /// <param ...