我们在开发游戏的过程中,通常都需要一些快捷的方式来进行一些非常规的测试,这些功能一般被称作控制台或者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的更多相关文章
-
Creator开源游戏、插件、教程、视频汇总
Creator开源游戏.插件.教程.视频汇总 来源 http://forum.cocos.com/t/creator/44782 王哲首席客服 17-03-17 4 史上最全,没有之一. ...
-
开发unity DLL插件
最近开发一款设备的SDK,想着要开发unity版本,怎么做呢?首先想到的就是在外部编写相关的驱动程序然后集成成几个dll作为unity的SDK使用了.So,我就开始了unity外部插件的研究之旅了. ...
-
浮动【电梯】或【回到顶部】小插件:iElevator.js
iElevator.js 是一个jquery小插件,使用简单,兼容IE6,支持UMD和3种配置方式,比锚点更灵活. Default Options _defaults = { floors: null ...
-
自制Unity小游戏TankHero-2D(5)声音+爆炸+场景切换+武器弹药
自制Unity小游戏TankHero-2D(5)声音+爆炸+场景切换+武器弹药 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm ...
-
自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析
自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm ...
-
自制Unity小游戏TankHero-2D(3)开始玩起来
自制Unity小游戏TankHero-2D(3)开始玩起来 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的.仅 ...
-
自制Unity小游戏TankHero-2D(2)制作敌方坦克
自制Unity小游戏TankHero-2D(2)制作敌方坦克 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的. ...
-
自制Unity小游戏TankHero-2D(1)制作主角坦克
自制Unity小游戏TankHero-2D(1)制作主角坦克 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的. ...
-
aBowman >;>;可以运用到自己博客上的小插件
大家进入我的博客会发现页面右边有一只小狗这部分.这个就是我用在上面的 一个小插件.插件网址是:http://abowman.com/google-modules/,这上面有很多的小插件,可以直接运用到 ...
随机推荐
-
[IOS]cocoapos 两个ruby源的对比
最近需要使用一些动态类库,cocoapods比较好用,能帮助管理这些类库,百度一下也能找到很多cocoapods配置方法,这里不赘述,我想要讲的是在配置的时候一般都会推荐这样做 $ gem sourc ...
-
DBCC CheckDB遇到a database snapshot could not be created
在备份一个客户的数据库时(数据库版本为SQL 2005 Express版本),做DBCC CHECKDB时遇到了下面错误信息: dbcc checkdb('DB_NAME'); 消息 5030,级别 ...
-
绝对实用 NAT + VLAN +ACL管理企业网络
在企业中,要实现所有的员工都能与互联网进行通信,每个人各使用一个公网地址是很不现实的.一般,企业有1个或几个公网地址,而企业有几十.几百个员工.要想让所有的员工使用这仅有的几个公网地址与互联网通信该怎 ...
-
Objective-C之优雅的命名
There are only two hard things in Computer Science: cache invalidation and naming things.在计算机科学中只有两件 ...
-
Android将应用程序的崩溃信息如何保存到本地文件,并上传服务器
导语:最近实在是太忙了,没有怎么更新公众号,也没有怎么认真去写一些内容,在这里先给关注我的朋友说一声抱歉,可能在接下来的一段时间,还是很忙,但是我会争取抽空多分享一下技术文章,给大家看,共同进步,也希 ...
-
FastDFS配置文件(tracker.conf)
# ===========================基本配置==================================== # 该配置文件是否生效 # false:生效 # true: ...
-
viewpager双层嵌套,子viewpager无限循环无法手动滑动
项目中首页是用viewpager+fragment集成的,第一个fragment有广告轮播图使用viewpager实现的,开始就遇到是广告图无法手动滑动,事件被外层的viewpager拦截响应切换到下 ...
-
Axure滚动效果实现
下面的这个透明区域用于显示滚动效果,它本身是一个处于隐藏状态的动态面板,它里面也放了一个动态面板用于产生移动的效果 里面的动态面板起名“实际内容”,注意它的默认状态是“状态2”,状态2和状态一的内容一 ...
-
iOS开发分析&;quot;秘密&;quot;App内容页面的效果(两)
@我前几天写的"秘密"的Cell制品的效果,想要的效果还是有差距,并且扩展性非常不好,于是重写封装,把总体视图都放到scrollView中,基本是和secret app 一模一样的 ...
-
ASP.Net TextBox控件只允许输入数字
原文:ASP.Net TextBox控件只允许输入数字 1.1.在Asp.Net TextBox 控件的 OnKeyPress 事件中指定输入键盘码必须为数字: <asp:TextBox ID= ...