综述
这已经是关于该编辑器的第三篇文章了,这一篇介绍添加一个新功能的完成的流程。我们已经知道这个编辑器的核心是【事件/环境/动作】那么当添加一个功能时我们的思考方向也要从这三部分入手,我们需要考虑这个功能的事件是什么环境是怎样的动作是什么,确定好这三个部分之后查看编辑器中有没有你需要的功能已经有了之后我们就能开始编写这些还没有的部分了。值得注意的是【事件/环境/动作】这三部分我们需要做一个从具体到抽象的过程。下面举一些例子。
第一个例子成就小试牛刀,功能描述:消灭一只野怪达成小试牛刀成就获取2个成就点100个金币。从描述中可以知道事件为消灭一只野怪没有环境动作为达成小试牛刀成就获取奖励。事件消灭一只野怪也就是单位死亡该单位指定为王国地图野怪,动作为达成成就改成就对应成就表中的id,这些编辑器中都有。
第二个例子玩家通过GM命令添加装备,功能描述:玩家通过聊天窗口向服务器发送指定格式的字符串为玩家添加装备。从描述中可知事件为玩家GM命令事件没有环境动作为给玩家添加装备。玩家GM命令事件可以最大限度的给使用者以自由定义比如自由定义起始字符串,动作由于是通过是通过聊天命令给玩家添加装备那么动作可以是玩家聊天命令然后选择一个玩家GM命令变量。
第三个例子成就海陆两开花,功能描述:当玩家拥有陆地霸主和海洋霸主成就时达成海陆两生花成就。从描述中可以知道事件为拥有陆地霸主成就或者海洋霸主成就(很明显这是一个触发器),环境为拥有陆地霸主成就且海洋霸主成就,动作为达成海陆两生花成就。这里的事件是让一个指定的触发器作为事件,环境是两个成就都存在(这里可以作为一个bool条件),动作是达成海陆两生花成就。
通过上面的三个例子可以看出事件其实就是开启该项功能的敲门砖(比喻可能并不恰当),环境可以理解为要执行的动作是否需要在指定的环境或条件下才成立,动作就是该功能要达成的目的。下面将会逐一介绍这三个例子的代码。
第一个例子--小试牛刀。
根据对第一个例子的解释我们的第一个事件是单位死亡。
1、首先写编辑器代码,关于编辑器代码我们知道要有原始数据和触发器数据。
a、首先分析一下关于事件的原始数据,根据原始数据结构组成我们需要给它一个名字、一个原始数据的唯一id、以及关于此原始数据的类型和描述,我们知道所有的原始数据id是通过自己定义的枚举值给出的这里在事件枚举(OrignalEventDataEnum)中定义一个单位死亡枚举。
我们此次用到的是世界中的野怪死亡,这里可以看出这里有一个指定的单位世界中的野怪,那么可以把这个野怪作为一个变量让编辑器使用者自行选择是什么单位死亡(应该注意的是我们每次写一个事件时应该考虑到如何将其抽象以便其他开发人员可以复用,如果还有其他类似的扩展只需在枚举类类添加类型就可以了),这里我们将这个死亡的单位作为了变量那么如何能让使用者选择到这个变量呢?下面会给出如何让使用者能够选择这个指定单位。我们已经定义了一个指定类型的单位死亡那么我们也可以定义一个即指定类型又指定具体单位死亡(这里我们无法真正的指定到某一个具体的事物我们只能指定到这个事物是什么比如世界野怪中的野狼、私人领地建筑中的麦田,可以看出这个类型是指定到具体的表,具体单位指定到这个表中的指定id),下面会给出这两个code。
让使用者能够选择一个指定的单位这里就要用到选择器这个定义,首先选择器是用小W自定义枚举类来实现的,关于这个显示器是怎样实现的有兴趣可以看另一篇文章关于枚举数据的介绍(点击这里),后面给出这个选择器的代码EnumItemType。
/*要用到的单位类型选择器,这里我们选择的单位是--王国地图野怪*/
/*调用方法--DataHandleManage.orignalHandle.getXuanZeQiById(选择器id)*/
/*显示该选择器的方法是DataHandleManage.openTypeSelectForm(指定选择器,确定选择执行的方法)*/
//使用指定的命名空间DataStruct
namespace DataStruct
{
public class EnumItemType : EnumClassParent
{
//(int)EnumType.ItemType选择器类型,每命名一个选择器需要定义对应的枚举值
public EnumItemType() : base("单位类型选择器", (int)EnumType.ItemType)
{
creat("私人领地建筑物", 10000);
creat("城镇建筑物", 10001);
creat("私人领地资源", 10100);
creat("城镇领地资源", 10101);
creat("玩家武将", 10200);//Hero
creat("私人领地士兵", 10201);
creat("城镇士兵", 10202);
creat("王国地图野怪", 10203);
creat("玩家达成成就",10204);
}
}
}
/// <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,
}
下面给出指定单位类型死亡code及指定单位类型和具体单位死亡code:
//需用指定给定的命名空间DataStruct
namespace DataStruct
{
/******指定单位类型和具体单位死亡的code************/
//需继承给定的父类OrigEventDataParent
public class OrigEventItemDetailDeath : OrigEventDataParent
{
private int item_type;
private int item_id;
private Button currentButton;
//这里的名字为:单位死亡(指定单位类型和具体单位),id为:(int)OrignalEventDataEnum.ItemDeathDetail
public OrigEventItemDetailDeath() : base("单位死亡(指定单位类型和具体单位)", (int)OrignalEventDataEnum.ItemDeathDetail)
{
descrip = "选择的指定单位死亡时触发该事件。";//描述
type = (int)OrigEventTypeEnum.单位;//类型,若是不知道该归为哪一类可以不给定
}
/************下面的save方法可以看过触发器数据后再来看****************/
public override TriggerDataParent Save(params object[] obj)
{
TriggerEventItemDetailBehavior behavior = DataHandleManage.editorHandle.CreatNewTriggerDataClass<TriggerEventItemDetailBehavior>(id);
behavior.setItemId(item_id, item_type);
return behavior;
}
/***************show方法是用来显示在panel上的数据的**************/
public override List<Control> Show()
{
List<Control> list = new List<Control>();
//创建一个Label用给定的方法DataHandleManage.orignalHandle.creatLabel
Label label = DataHandleManage.orignalHandle.creatLabel(name + ",点击可选择一个单位类型。");
list.Add(label);
//创建一个 Button用给定的方法DataHandleManage.orignalHandle.creatButton
Button button = DataHandleManage.orignalHandle.creatButton("点击选择");
button.MouseClick += ButtonClick;//点击button事件触发的方法
list.Add(button);
Label label1 = DataHandleManage.orignalHandle.creatLabel(" 请输入具体单位id:");
list.Add(label1);
//创建一个TextBox用给定的方法DataHandleManage.orignalHandle.creatTextBox
TextBox textBox = DataHandleManage.orignalHandle.creatTextBox();
textBox.TextChanged += (object sender, EventArgs e) =>
{
string str = textBox.Text;
try
{
item_id = Convert.ToInt32(str);
}
catch (FormatException)
{
MessageBox.Show("请输入正确的数字");
}
catch (OverflowException)
{
MessageBox.Show("请输入正确的数字");
}
};
list.Add(textBox);
return list;
}
private void ButtonClick(object sender, MouseEventArgs e)
{
currentButton = (Button)sender;
FuncDelegate func = new FuncDelegate(selectUIType);
//DataHandleManage.openTypeSelectForm是一个给定的方法用来显示选择器,
//第一个参数给定一个选择器,第二个参数选择好选择其中的内容后调用的方法,这个方法有指定的委托
DataHandleManage.openTypeSelectForm(DataHandleManage.orignalHandle.getXuanZeQiById((int)EnumType.ItemType), func);
}
private void selectUIType(params object[] uiType)
{
EnumItem item = (EnumItem)uiType[0];
this.item_type = item.value;
currentButton.Text = item.name + "--点击选择";
}
}
/******指定单位类型死亡的code************/
public class OrigEventItemTypeDeath : OrigEventDataParent
{
private int item_type;
private Button currentButton;
public OrigEventItemTypeDeath() : base("单位死亡(指定单位类型)", (int)OrignalEventDataEnum.ItemDeath)
{
descrip = "选择的指定单位类型死亡时触发该事件。";
type = (int)OrigEventTypeEnum.单位;
}
public override TriggerDataParent Save(params object[] obj)
{
TriggerEventItemTypeBehavior behavior = DataHandleManage.editorHandle.CreatNewTriggerDataClass<TriggerEventItemTypeBehavior>(id);
behavior.setItemId(item_type);
return behavior;
}
public override List<Control> Show()
{
List<Control> list = new List<Control>();
Label label = DataHandleManage.orignalHandle.creatLabel(name + ",点击可选择一个单位类型。");
list.Add(label);
Button button = DataHandleManage.orignalHandle.creatButton("点击选择");
button.MouseClick += ButtonClick;
list.Add(button);
return list;
}
private void ButtonClick(object sender, MouseEventArgs e)
{
currentButton = (Button)sender;
FuncDelegate func = new FuncDelegate(selectUIType);
DataHandleManage.openTypeSelectForm(DataHandleManage.orignalHandle.getXuanZeQiById((int)EnumType.ItemType ), func);
}
private void selectUIType(params object[] uiType)
{
EnumItem item = (EnumItem)uiType[0];
this.item_type = item.value;
currentButton.Text = item.name + "--点击选择";
}
}
}
然后我们分析一下关于动作的原始数据 。由小试牛刀功能可知我们的最终动作是达成小试牛刀成就,这个小试牛刀成就很明显对应于成就表中的某一个id,我们可以定义以下代码:
namespace DataStruct
{
//动作类原始数据需继承动作原始数据的父类OrigActionDataParent
public class OrigActionReachChengJiu : OrigActionDataParent
{
/// <summary>
/// 成就对应表的id
/// </summary>
private float achievement_id;
public OrigActionReachChengJiu() : base("达成成就", (int)OrignalActionDataEnum.ReachChengJiu)
{
descrip = "选择达成的某中成就";
type = (int)OrigActionTypeEnum.成就;
}
public override TriggerDataParent Save(params object[] obj)
{
if (achievement_id == -1)
{
MessageBox.Show("请选择要达成的成就");
return null;
}
TriggerActionReachChengJiu chengJiu = DataHandleManage.editorHandle.CreatNewTriggerDataClass<TriggerActionReachChengJiu>(id);
chengJiu.SetAchievementId(achievement_id);
return chengJiu;
}
public override List<Control> Show()
{
achievement_id = -1;
List<Control> list = new List<Control>();
Label label = DataHandleManage.orignalHandle.creatLabel(name + ",请填写达成的成就对应的表的id");
list.Add(label);
TextBox textBox = DataHandleManage.orignalHandle.creatTextBox();
textBox.TextChanged += (object sender, EventArgs e) =>
{
string str = textBox.Text;
try
{
achievement_id = Convert.ToSingle(str);
}
catch (FormatException)
{
MessageBox.Show("请输入正确的数字");
}
catch (OverflowException)
{
MessageBox.Show("请输入正确的数字");
}
};
list.Add(textBox);
return list;
}
}
}
b、然后我们来分析触发器数据
触发器数据:由上述的事件分析可知在OrigEventItemTypeDeath类中我们额外定义了一个变量item_type用于选择指定的单位(在此次的选择中我们使用的是王国地图野怪),那么对应的触发器也需要一个个指定的变量;同理对于OrigEventItemDetailDeath中的两个额外变量触发器中也要有对应的两个变量用于数据存储;它们分别对应的触发器类是TriggerEventItemTypeBehavior和TriggerEventItemDetailBehavior代码如下:
namespace DataStruct
{
/// <summary>
/// 事件--单位行为
/// 可对应于原始数据中事件的单位的等级、采集、使用及成就......
/// </summary>
public class TriggerEventItemTypeBehavior : TriggerEventDataParent
{
public float item_type;
public TriggerEventItemTypeBehavior() { }
public TriggerEventItemTypeBehavior(float origId) : base(origId) { }
public void setItemId(int item_id)
{
this.item_type = item_id;
}
public override string Descript()
{
string des = "";
OrigEventDataParent orig = AllDataManage.origData.dicEventData[(int)origId];
des = orig.Descrip() + "(单位id=" + item_type + ")";
return des;
}
}
public class TriggerEventItemDetailBehavior : TriggerEventDataParent
{
public float item_type;
public float item_id;
public void setItemId(int item_id, int item_type)
{
this.item_id = item_id;
this.item_type = item_type;
}
public override string Descript()
{
string des = "";
OrigEventDataParent orig = AllDataManage.origData.dicEventData[(int)origId];
des = orig.Descrip() + "(单位类型=" + item_type + ",单位id=" + item_id + ")";
return des;
}
}
}
至此我们已经定义完编辑器中的所有代码。下面要定义客户端或者服务器运行时代码。
2、根据功能我们可知此段数据只需在服务器运行时,这里我们只给出服务器运行时代码。在运行时中我们对于原始数据的处理是将其对应的唯一id即原始数据枚举中对应的枚举值作为常量来使用,对于触发器的处理是将所有的(处方法外)copy到运行时并实现运行时中规定的方法,下面来具体介绍。
上面我们定义两个单位死亡事件的原始数据,一个是指定单位类型与指定单位类型和具体单位的死亡,将这两个单位死亡的对应的枚举值作为常量写道运行时指定的位置即可;同理我们还定义了一个达成成就的动作原始数据将其写道指定位置。代码如下:
/*该类所在位置server_game\GameEditorRunTime\com\dataStruct\orignalEnumType*/
/*关于原始数据的事件、环境、动作均在该文件夹下*/
public class EnumOrignalEventData {
/**
* 单位死亡2604
*/
public static final int ItemDeath = 2604;
/**
* 指定单位死亡
*/
public static final int ItemDeathDatail = 2612;
}
public class EnumOrignalActionData
{
/**
* 达成成就
*/
public static final int ReachChengJiu = 1500;
}
除了原始数据外我们还定义了自定义的选择器枚举类,在本次功能中我们定义了单位类型选择器,在服务器运行时中我们依然采用常量的做法来标识数据代码如下:
/*该类所在的位置server_game\GameEditorRunTime\com\dataStruct\orignaltypeSelectEnum*/
/*以后新建的关于选择器转变过来的类均创建到该文件夹下*/
public class EnumItemType
{
/**
* 私人领地建筑物10000
*/
public static final int privateBuild=10000;
/**
* 城镇建筑物10001
*/
public static final int townBuild=10001;
/**
*私人领地资源10100
*/
public static final int privateResource=10100;
/**
* 城镇领地资源10101
*/
public static final int townResource=10101;
/**
*玩家武将10200
*/
public static final int playerHero=10200;
/**
* 私人领地士兵10201
*/
public static final int privateSolider=10201;
/**
*城镇士兵10202
*/
public static final int townSolider=10202;
/**
* 王国地图野怪10203
*/
public static final int worldMapYeGuai=10203;
}
下面介绍两个对应的触发器数据,需要注意的是由于我们定义的数据读取原因类的名字必须保持一致,代码如下:
/*下面的关于事件的类所在的位置为server_game\GameEditorRunTime\com\dataStruct\triggerData\Event\normal*/
/*关于动作所在的类的位置为server_game\GameEditorRunTime\com\dataStruct\triggerData\Action\normal*/
/*关于环境的类所在的位置为server_game\GameEditorRunTime\com\dataStruct\triggerData\Condition\normal*/
/*为了方便管理以后所创建的新类请在对应的文件夹下创建新的文件夹再创建类。
*比如创建一个新的事件类可根据需要在
*server_game\GameEditorRunTime\com\dataStruct\triggerData\Event文件夹下创建新的文件夹
*然后再创建你需要的新类,
*动作的是server_game\GameEditorRunTime\com\dataStruct\triggerData\Action文件夹下
*创建新的文件夹,
*环境的是server_game\GameEditorRunTime\com\dataStruct\triggerData\Condition文件夹下
*创建新的文件夹。
*这里为了直观表示用了文件夹实际操作过程是创建新的包*/
@Service
@Scope("prototype")
/*触发器的名字要与编辑器中触发器的类名一致,需继承事件触发器父类*/
public class TriggerEventItemDetailBehavior extends TriggerEventDataParent {
/**
* EnumItemType中对应的值
*/
public float item_type;
public float item_id;
/**
*必须实现的数据处理类用于判断传入的事件数据是否是编辑者给定的数据
*关于数据如何处理需开发者自行定义
*最后返回判定结果true或者false
*这里传入的第二个参数是需要开发者自己定义的,后面会给出怎样写方法来传入所需数据
* @param obj:obj[0]itme_type,obj[1]item_id
*/
@Override
public boolean DataProcessing(float origId, Object... obj) {
if ((int) origId != (int) this.origId) {
return false;
}
if (obj.length < 2) {
return false;
}
if ((int) obj[0] != (int) item_type) {
return false;
}
if ((int) obj[1] == (int) item_id) {
return true;
}
return false;
}
}
@Scope("prototype")
@Service
public class TriggerEventItemTypeBehavior extends TriggerEventDataParent {
/**
* EnumItemType中对应的值
*/
public float item_type;
@Override
public boolean DataProcessing(float origId, Object... obj) {
Tool.print_debug_level0("TriggerEventItemTypeBehavior:origId=" + origId + ",this.origId=" + this.origId);
if ((int) origId != (int) this.origId) {
return false;
}
if (obj != null && obj.length > 0) {
if ((int) obj[0] == (int) item_type) {
Tool.print_debug_level0("TriggerEventItemTypeBehavior中判断结果为真。");
return true;
}
}
Tool.print_debug_level0("TriggerEventItemTypeBehavior中判断结果为假。");
return false;
}
}
@Scope("prototype")
@Service
/*这里定义了对应的动作类触发器,触发器的名字要与编辑器中触发器的类名一致,需继承动作触发器父类*/
public class TriggerActionReachChengJiu extends TriggerActionDataParent
{
/// <summary>
/// 成就对应表的id
/// </summary>
public float achievement_id;
/*关于动作触发器的数据处理,这里统一给传入了一个参数玩家的id,通过该id你可以获取玩家所有的数据来处理关于该触发器中的动作*/
@Override
public boolean DataProcessing(int userId)
{
Tool.print_debug_level0("达成成就,成就对应的表的id=" + achievement_id);
return true;
}
}
至此我们已经定义好关于运行时的代码现在我们还缺少一个重要的环节那就是我们定义了一个单位死亡的事件如何调用怎么调用该事件,下面将要介绍的就是这个。
/*下面的类所在的位置是server_game\src\main\java\com\wyl\naval\dataread*/
public interface GameRunTimeService {
/**
* 指定类型的单位死亡
* @param userId
* @param itemType单位类型
*/
public void itemDeathType(int userId,int itemType);
/**
* 指定单位类型与单位的srcId的单位死亡
* @param userId
* @param itemType单位类型
* @param itemId单位的srcId
*/
public void itemDeathTypeDetail(int userId,int itemType,int itemId);
}
@Service
public class GameRunTimeImpl implements GameRunTimeService
{
/*userId表示玩家的id,origId表示原始数据的事件类id,objs是事件处理所需的数据*/
public boolean receiveEventTrigger(int userId, float origId, Object... objs) {
boolean res = false;
/*这里省略处理的代码*/
return res;
}
@Override
public void itemDeathType(int userId, int itemType) {
/*这里的事件数据所需的数据是死亡单位的类型*/
receiveEventTrigger(userId, EnumOrignalEventData.ItemDeath, itemType);
}
@Override
public void itemDeathTypeDetail(int userId, int itemType, int itemId) {
/*这里的事件数据所需的数据是死亡单位的类型和死亡单位的srcId*/
receiveEventTrigger(
userId, EnumOrignalEventData.ItemDeathDatail, itemType, itemId);
}
}
给出了事件调用的方法就可以再需要的地方来调用给出的对应的方法,至此一个完整的功能已经开发完成,从中我们可看到再开发一个所需的功能时我们首先要分析该功能找出这个功能的事件/环境/动作,然后将这些再细化最后再抽象能够最大限度的让后来者使用或者让后来者能够再此基础上花费最少的时间做出所需的功能。
第二个例子--玩家通过GM命令添加装备