该编辑器编写的初衷是为了帮助游戏一些功能的开发比如新手引导,成就等等。现在编写的是一个框架,具体功能需要自行扩展。
目录
一、编辑器结构(原始数据+触发器)
1、编辑器总体结构
2、原始数据
3、触发器数据
4、枚举数据
5、编辑数据存储
二、运行时结构
三、一个完整的实例
一、编辑器结构(原始数据+触发器)
1、编辑器总体结构
编辑器有四大数据模块组成它们分别是原始数据、触发器数据、枚举数据结构、数据存储数据结构。下面会逐一介绍每个数据,关于它们的数据构成如何扩展等。
原始数据是最基本的数据,它是组成整个编辑器数据的基石,标识出该数据是做什么的。
触发器数据在编辑器和运行时都是需要用的数据,在编辑器中它用来存储编辑好的数据,在运行时中它用来处理游戏传出的数据。可以看出触发器数据在整个编辑器和运行时中有着重要的地位。
枚举数据结构是用于辅助类的数据,该数据用于选择器数据。
数据存储 分为编辑数据存储和导出数据存储,编辑数据用于保存编辑器数据,导出数据用于运行时数据的运用。
2、原始数据
原始数据有一个总类OrigDataParent。
OrigDataParent是一个抽象类,包含四个字段,变量name表示数据的名称,它将在选择[事件/环境/动作]中显现该名字。变量id表示该段数据的唯一id,它对应着不同的枚举值,[事件/环境/动作]对应的不同的枚举,这个id代表该唯一的原始数据,在触发器中的origId就是对应着该id,[事件/环境/动作]三个不同的枚举在运行时中有着唯一区别的作用。变量descrip表示数据描述,用来给使用者一个具体的解释。变量type表示所属类型它用于为原始数据分类型,对应着三个不同的枚举分别是OrigEventTypeEnum/OrigConditionTypeEnum/OrigActionTypeEnum。
OrigDataParent继承了接口IOrigData,该接口包含了两个方法Show()和Save (params object[] obj),其中Show用来显示原始数据中的内容供使用者填写/选择数据,后面将会举例说明。Save用来保存使用者编辑的数据。
另外比较重要的一点是为了能够读取数据的方便开发者扩展新的触发器时需要给出指定的命名空间DataStruct。
下面是元数据OrigDataParent与接口IOrigData的代码。
public interface IOrigData{/// <summary>/// 显示Panel数据/// </summary>/// <returns></returns>List<Control> Show();/// <summary>/// 保存数据/// </summary>/// <param name="obj"></param>TriggerDataParent Save (params object[] obj) ;}namespace DataStruct
{[Instantiation]public abstract class OrigDataParent : IOrigData{public string name;//数据名称/// <summary>/// 唯一id/// 对应ThreeOrigEnumType中三个枚举中的值/// </summary>public int id;/// <summary>/// 数据描述/// </summary>public string descrip;/// <summary>/// //所属类型,默认全部/// 对应枚举/// OrigEventTypeEnum/OrigConditionTypeEnum/OrigActionTypeEnum/OrigOtherTypeEnum/// 的值/// </summary>public int type = 0;public OrigDataParent(string name, int id){this.name = name;this.id = id;}/ <summary>/ 保存数据的方法/ </summary>/ <param name="obj">obj[0]=EditorTriggerDataStruct</param>//public abstract void Save(params object[] obj);public abstract TriggerDataParent Save(params object[] obj);/// <summary>/// 显示内容的方法/// </summary>/// <returns></returns>public abstract List<Control> Show();public virtual string Descrip(){return name + "——" + descrip;}public override string ToString(){return name;}}
}
原始数据下面分三个类型,事件、环境、动作,这三个类型都有一个父类它们分别是OrigEventDataParent、OrigConditionDataParent、OrigActionDataParent它们都继承父类OrigDataParent。这三个类也均是抽象类,具体内容可见项目代码。
3、触发器数据
触发器数据有一个总类TriggerDataParent,它是一个抽象类。
TriggerDataParent中有两个字段,变量origId它对应于原始数据唯一的id,标识该触发器要用于做什么。变量fullName表示该触发器的完全限定名,该字段不需要开发者维护,该字段仅用于数据的存储与读取。另外比较重要的一点是为了能够读取数据的方便开发者扩展新的触发器时需要给出指定的命名空间DataStruct。下面是该抽象类的具体内容。
public interface IListVoluation{/// <summary>/// 传入一个List<object> 类型/// 将其转化为list具体类型/// </summary>/// <param name="list"></param>void listVoluation(List<object> list);}namespace DataStruct
{/// <summary>/// 触发器数据总类/// </summary>[TriggerTypeAttri]public abstract class TriggerDataParent : IListVoluation{/// <summary>/// 对应原始数据的唯一id/// </summary>public float origId;public string fullName;public TriggerDataParent(float origId){this.origId = origId;fullName = GetType().FullName;}public TriggerDataParent(){fullName = GetType().FullName;}public void SetOrigId(int origId){this.origId = origId;}/// <summary>/// 创建额外树节点,/// 这个节点将作为本身节点的子节点/// 可以参考TriggerMultipleConditionData的重写方法--creatSelfTreeNode/// 如无特殊需要不必重写/// </summary>/// <returns></returns>public virtual TreeNode creatSelfTreeNode(){return null;}/// <summary>/// 对触发器的额外描述/// 如无特殊需要不必重写/// </summary>/// <returns></returns>public virtual string Descript(){return null;}/// <summary>/// 如果子类中有List成员变量,请务必实现该方法/// 传入一个List<object> 类型,将其转化为list具体类型/// </summary>/// <param name="list">传入的list是对应于所需要的list,用于数据读取</param>public virtual void listVoluation(List<object> list){}}
}
触发器结构下面分三个类型,事件、环境、动作,这三个类型都有一个父类它们分别是TriggerDataParent、TriggerConditionDataParent、TriggerActionDataParent它们都继承父类TriggerDataParent。这三个类也均是抽象类,具体内容可见项目代码。
4、枚举数据
枚举数据是自定义的一个类EnumClassParent,它用来作为选择器数据,所有的选择器数据都要继承该父类,这个类不参与存储,在编辑器开始运行时将会运用反射自动加载所有继承该类的子类。该类有四个字段,它们分别是:listEnum它是一个list列表类型是EnumItem,用来缓存枚举单位;name它表示选择器的名字;enumType枚举类型值,对应于EnumType枚举中的值;字典dicEnums。下面给出该代码:
public class EnumItem{public string name;public int value;public EnumItem(string name, int value){this.name = name;this.value = value;}}[Instantiation]public abstract class EnumClassParent{public List<EnumItem> listEnum = new List<EnumItem>();public string name;/// <summary>/// 对应于EnumType的值/// </summary>public int enumType;/// <summary>/// /key表示EnumItem中的value值/// </summary>[JsonIgnore]public Dictionary<int, EnumItem> dicEnums = new Dictionary<int, EnumItem>();public EnumClassParent(string name, int enumType){this.name = name;this.enumType = enumType;}protected void creat(string name, int value = -1){if (value == -1){value = listEnum.Count;}EnumItem item = new EnumItem(name, value);listEnum.Add(item);if (dicEnums.ContainsKey(item.value)){dicEnums[item.value] = item;}else{dicEnums.Add(item.value, item);}}public void setData(){if (listEnum.Count > 0){foreach (var item in listEnum){if (dicEnums.ContainsKey(item.value)){dicEnums[item.value] = item;}else{dicEnums.Add(item.value, item);}}}}}/// <summary>/// 枚举类型标识/// </summary>public enum EnumType{/// <summary>/// 对比计算器/// </summary>ContrastCalculator,/// <summary>/// ui的类型/// </summary>UIType,/// <summary>/// 触发器枚举/// </summary>Trigger,/// <summary>/// 创建某个物体的枚举/// </summary>CreatItem,/// <summary>/// 单位类型/// </summary>ItemType,/// <summary>/// 玩家GM命令/// </summary>PlayerGM,}
下面给出一个具体的选择器的代码:
namespace DataStruct
{/// <summary>/// 对比计算器/// </summary>public class EnumContrastCalculator : EnumClassParent{public EnumContrastCalculator() : base("对比计算器", (int)EnumType.ContrastCalculator){creat("等于",1000);creat("不等于",1001);creat("小于等于",1002);creat("大于等于",1003);creat("小于",1004);creat("大于",1005); }}
}
改代码在编辑器运行是对应的数据显示:

5、编辑数据存储
编辑器数据的存储结构由三个类构成,EditorTriggerDataStruct表示一个完整的触发器,其中包括该触发器内所有的事件环境动作数据,EditorData表示整个类的数据结构,EditorDataSave存储所有的类的数据同时也是保存的json数据。下面的一张图将会解释这三个类的关系。

该数据不需要开发者再次管理,整套管理流程已经书写完毕。
二、运行时结构
对于不同的游戏运行时结构会有所不同,现在给出一个特定的运行时结构。该运行时是在服务器使用。
1、编辑器导出数据的结构
ExportTriggerDataStruct类作为一个触发的结构它包括事件/环境/条件着三类数据,变量triggerStructIndex表示该触发器在该工程中的唯一序列,用于区别于其他的导出触发器数据结构。ExportTriggerDataStruct中有三个方法它们用于处理事件/环境/动作这些数据。下面给出代码:
@DataStructAnnotat
@Scope("prototype")
@Service
public class ExportTriggerDataStruct implements IListVoluation
{public ArrayList<TriggerEventDataParent> list_event = new ArrayList<TriggerEventDataParent>();public ArrayList<TriggerConditionDataParent> list_condition = new ArrayList<TriggerConditionDataParent>();public ArrayList<TriggerActionDataParent> list_action = new ArrayList<TriggerActionDataParent>();public String fullName;public float triggerStructIndex;@Overridepublic void listVoluation(ArrayList<Object> list){if (list == null || list.size() <= 0){return;}for (Object obj : list){addTriggerData((TriggerDataParent) obj);}}private void addTriggerData(TriggerDataParent data){if (data instanceof TriggerEventDataParent){TriggerEventDataParent de = (TriggerEventDataParent) data;de.triggerIndex = list_event.size();de.triggerStructIndex = triggerStructIndex;list_event.add(de);}else if (data instanceof TriggerConditionDataParent){TriggerConditionDataParent dc = (TriggerConditionDataParent) data;list_condition.add(dc);}else if (data instanceof TriggerActionDataParent){TriggerActionDataParent da = (TriggerActionDataParent) data;list_action.add(da);}}/*** 接受处理事件的结果* * @param index* @param res*/public boolean receiveEventResult(int userId, int index, boolean res){if (res){return judgeConditionDataParocessing(userId);}return false;}/*** 判断环境数据的处理结果* @param userId* @return*/public boolean judgeConditionDataParocessing(int userId){if (list_condition != null && list_condition.size() > 0){for (TriggerConditionDataParent con : list_condition){boolean res = con.DataProcessing(userId);if (!res){Tool.print_debug_level0("条件不满足不执行动作。对应的原始数据id=" + con.origId);return false;}}}return actionDataProcessing(userId);}/*** 执行动作* @param userId* @return*/public boolean actionDataProcessing(int userId){if (list_action != null && list_action.size() > 0){for (TriggerActionDataParent data : list_action){boolean res = data.DataProcessing(userId);Tool.print_debug_level0("执行动作后的反馈。res=" + res + ",ActionData origId=" + data.origId);}}return true;}}
ExportData类中包含整个工程中所有的ExportTriggerDataStruct,我们从json数据中读取的最后会得到一个ExportData类,ExportData类继承了一个IListVoluation接口。listVoluation方法的作用见IListVoluation接口中的方法。代码如下:
@DataStructAnnotat
@Scope("prototype")
@Service
public class ExportData implements IListVoluation
{public ArrayList<ExportTriggerDataStruct> listChuFaQi;public String fullName;@Overridepublic void listVoluation(ArrayList<Object> list){if (list == null || list.size() <= 0){return;}listChuFaQi = new ArrayList<ExportTriggerDataStruct>();for (Object data : list){if (data instanceof ExportTriggerDataStruct){ExportTriggerDataStruct export = (ExportTriggerDataStruct) data;listChuFaQi.add(export);}}Tool.print_debug_level0("数据加载完成!!!");}}public interface IListVoluation
{/*** 传入一个List<object> 类型 将其转化为list具体类型* 若子类中有ArrayList或者其他的集合请务必实现该方法* 否则会导致数据丢失* @param list*/void listVoluation(ArrayList<Object> list);
}
由于在这个应用中我们遇到了类的继承目前为止小W并没有找到合适的此类的json数据解析,所以就自己写了一个json数据解析,小W会再写另一边文章介绍这类json的解析,有兴趣的可以看一下。
2、触发器数据结构的介绍
触发器数据用一个共同的父类TriggerDataParent这个类是一个抽象的于上述的编辑器的TriggerDataParent是一样的,只是方法有所区别,详细的见项目。
事件:TriggerEventDataParent继承父类TriggerDataParent,其中有一个数据处理类用于处理该事件的中传过来的数据,该数据具体怎样处理有开发者自己所需决定。listVoluation方法的作用见IListVoluation接口中的方法。它的代码如下:
public abstract class TriggerEventDataParent extends TriggerDataParent
{/// <summary>/// 数据在所在的触发器结构中的唯一次序/// 该数值在程序启动后自动生成/// 不需要额外维护/// </summary>public int triggerIndex;/// <summary>/// 该数据所在触发器结构自己的顺序/// 该数据在游戏启动时自动赋值/// 无需额外维护/// </summary>public float triggerStructIndex;@Overridepublic void listVoluation(ArrayList<Object> list){}/*** 每个事件子类均需要实现该方法* * @param origId* 原始数据对应的唯一id* @param obj* 需要的参数,需要转化为自己需要的类型* @return*/public abstract boolean dataProcessing(float origId, Object... obj);
}
环境:TriggerConditionDataParent有一个数据处理的方法用于处理该环境中的数据。代码如下:
public abstract class TriggerConditionDataParent extends TriggerDataParent
{/*** 实现条件类,所有条件子类均需实现该抽象方法* * @return*/public abstract boolean dataProcessing(int userId);
}
动作:TriggerActionDataParent有一个数据处理的方法用于处理该环境中的数据。代码如下:
public abstract class TriggerActionDataParent extends TriggerDataParent
{/*** 实现动作类,所有动作子类均需实现该抽象方法* * @return*/public abstract boolean DataProcessing(int userId);
}
3、游戏运行时数据处理
运行时需要用的是关于触发器的类,而关于原始数据的类作为一个枚举值或者常量作为原始数据。
GameRunTimeProcessing中包含一个工程中的数据,这其中有一个获取到事件数据的方法receiveEventTrigger,代码如下:
/*** 游戏数据处理* * @author Will**/
public class GameRunTimeProcessing {/*** 所有触发器结构 key:triggerStructIndex*/private HashMap<Float, ExportTriggerDataStruct> map_allTriggerStructs = new HashMap<Float, ExportTriggerDataStruct>();/*** 所有的事件数据 key:origId*/private HashMap<Float, ArrayList<TriggerEventDataParent>> map_allEventDtas = new HashMap<Float, ArrayList<TriggerEventDataParent>>();public GameRunTimeProcessing() {}public GameRunTimeProcessing(Object obj) {if (obj instanceof ExportData) {ExportData data = (ExportData) obj;if (data.listChuFaQi != null && data.listChuFaQi.size() > 0) {for (ExportTriggerDataStruct export : data.listChuFaQi) {map_allTriggerStructs.put(export.triggerStructIndex, export);addAllEventData(export.list_event, export.triggerStructIndex);}}}}private void addAllEventData(ArrayList<TriggerEventDataParent> list, float triggerStructIndex) {if (list == null || list.size() <= 0) {return;}for (TriggerEventDataParent trigger : list) {trigger.triggerStructIndex = triggerStructIndex;if (map_allEventDtas.containsKey(trigger.origId)) {map_allEventDtas.get(trigger.origId).add(trigger);} else {ArrayList<TriggerEventDataParent> trigger_list = new ArrayList<TriggerEventDataParent>();trigger_list.add(trigger);map_allEventDtas.put(trigger.origId, trigger_list);}}}/*** 获取事件触发器* @param userId 玩家id* @param origId 原始数据id* @param objs 事件触发器处理数据所需的参数,它是一个数组* @return*/public boolean receiveEventTrigger(int userId, float origId, Object... objs) {Tool.print_debug_level0("接受到事件触发器。origId" + origId + ",map_allEventDtas.size()=" + map_allEventDtas.size());if (map_allEventDtas.containsKey(origId)) {Tool.print_debug_level0("map_allEventDtas.get(origId).size()=" + map_allEventDtas.get(origId).size());if (map_allEventDtas.get(origId).size() > 0) {for (TriggerEventDataParent data : map_allEventDtas.get(origId)) {boolean res = data.DataProcessing(origId, objs);ExportTriggerDataStruct trigger_st = map_allTriggerStructs.get(data.triggerStructIndex);boolean returnRes = trigger_st.receiveEventResult(userId, data.triggerIndex, res);Tool.print_debug_level0("事件发生之后处理数据的结果=" + returnRes);return returnRes;}} else {return false;}}return false;}}
三、一个完整的实例
1、玩家GM命令为玩家添加装备
描述:在聊天的输入框输入指定的字符串获取装备。
a、原始数据结构
原始数据事件,代码如下:
//命名空间固定为DataStruct
namespace DataStruct
{/// <summary>/// 原始事件数据需继承原始数据事件类OrigEventDataParent/// </summary>public class OrigEventPlayerGMStr : OrigEventDataParent{private string GM_str;//玩家GM命令起始字符串public OrigEventPlayerGMStr() : base("玩家GM命令事件(指定起始字符串)", (int)OrignalEventDataEnum.playerGM_str){descrip = "通过聊天窗口添加命令,比如(添加装备):add ";type = (int)OrigEventTypeEnum.玩家;}/// <summary>/// 保存触发器数据/// </summary>/// <param name="obj"></param>/// <returns></returns>public override TriggerDataParent Save(params object[] obj){TriggerEventPlayerGMStr data = DataHandleManage.editorHandle.CreatNewTriggerDataClass<TriggerEventPlayerGMStr>(id);data.SetData(GM_str);return data;}/// <summary>/// 再panel中显示的数据,下面的图片中将会给出这个方法的用处/// </summary>/// <returns></returns>public override List<Control> Show(){List<Control> list = new List<Control>();Label label = DataHandleManage.orignalHandle.creatLabel(name + "请输入GM命令起始字符(请尽量不要与其它已用字符重复):");list.Add(label);TextBox textBox2 = DataHandleManage.orignalHandle.creatTextBox();textBox2.TextChanged += (object sender, EventArgs e) =>{GM_str = textBox2.Text;};list.Add(textBox2);return list;}}
}
运行图片如下:

注意上述图片中的内容是如何显示的按钮是如何调用save方法的都不需要开发者去处理,内部已经处理好了。在此玩家GM命令中不需要环境。
原始数据动作,代码如下:
//指定命名空间DataStruct
namespace DataStruct
{
//原始数据动作继承动作的原始数据父类,此类已经在前面介绍过OrigActionDataParentpublic class OrigActionPlayerChatCommand : OrigActionDataParent{private int player_GM;//玩家GM命令字符串private Button currentButton;//当前选中的buttonpublic OrigActionPlayerChatCommand() : base("玩家聊天命令", (int)OrignalActionDataEnum.playerChatCommand){descrip = "给玩家添加装备。";type = (int)OrigActionTypeEnum.玩家;}/// <summary>/// 保存触发器数据/// </summary>/// <param name="obj"></param>/// <returns></returns>public override TriggerDataParent Save(params object[] obj){TriggerActionPlayerChatCommand data = DataHandleManage.editorHandle.CreatNewTriggerDataClass<TriggerActionPlayerChatCommand>(id);data.SetData(player_GM);return data;}/// <summary>/// 再panel中显示的数据,下面的图片中将会给出这个方法的用处/// </summary>/// <returns></returns>public override List<Control> Show(){List<Control> list = new List<Control>();//这里已经提供创建Label的方法,使用Label时使用该方法Label label = DataHandleManage.orignalHandle.creatLabel(name + ",点击可选择一个聊天命令类型。");list.Add(label);//这里已经提供创建Button的方法,使用Button时使用该方法Button button = DataHandleManage.orignalHandle.creatButton("点击选择");button.MouseClick += ButtonClick;list.Add(button);return list;}//点击Button的事件private void ButtonClick(object sender, MouseEventArgs e){currentButton = (Button)sender;FuncDelegate func = new FuncDelegate(selectUIType);DataHandleManage.openTypeSelectForm(AllDataManage.allEnumClass.dicEnumParent[(int)EnumType.PlayerGM], func);}private void selectUIType(params object[] uiType){EnumItem item = (EnumItem)uiType[0];this.player_GM = item.value;currentButton.Text = item.name + "--点击选择";}}
}
运行图片如下:

b、触发器代码结构
触发器数据事件,代码如下:
//使用指定命名空间DataStruct
namespace DataStruct
{//该事件触发器需继承触发器事件类 TriggerEventDataParentpublic class TriggerEventPlayerGMStr : TriggerEventDataParent{public string GM_str;public void SetData(string GM_str){this.GM_str = GM_str;}//描述该触发器内容,若有该触发器自己的自定义的变量则需要重写该方法用于展示该条触发器信息public override string Descript(){string des = "";OrigEventDataParent orig = AllDataManage.origData.dicEventData[(int)origId];//string name = AllDataManage.allEnumClass.dicEnumParent[(int)EnumType.PlayerGM].dicEnums[(int)player_GM].name;des = orig.name + "(自定义的起始命令:" + this.GM_str + ")";return des;}}
}
触发器数据动作,代码如下:
//使用指定的命名空间DataStruct
namespace DataStruct
{
//该动作触发器需继承触发器动作类TriggerActionDataParentpublic class TriggerActionPlayerChatCommand : TriggerActionDataParent{public float player_GM;public void SetData(int player_GM){this.player_GM = player_GM;}//描述该触发器内容,若有该触发器自己的自定义的变量则需要重写该方法用于展示该条触发器信息public override string Descript(){string des = "";OrigActionDataParent orig = AllDataManage.origData.dicActionData[(int)origId];string name = AllDataManage.allEnumClass.dicEnumParent[(int)EnumType.PlayerGM].dicEnums[(int)player_GM].name; ;des = orig.name + "(" + name + ")";return des;}}
}
运行图片如下:




















