1.概述
上次我们一口气讲到下层组件讲多点触控的消息根据具体的需求,在每个时刻将其抽象画成单一指令。
接下来我们要聊聊,上层组件中如何将单一的指令应用在角色上,让角色在移动,普通攻击和释放技能中间做出正确的行为。
上次我们也说过了,上层组件是个优先级队列,队列中分别有对移动,普通攻击,技能释放的判定检测组件,按照 技能> 普攻 > 移动的优先级,对下层组件传递过来的消息依次,逐个进行处理。
按照这种设计思路,我们首先还是应该有一个接口,统一组建的操作,然后在响应事件的时候对组件for循环处理
2.队列组件接口
public enum TouchEventType
{
down,
press,
up,
moveEnd,
}
public class ManualControllerCom
{
protected Units self;//绑定的角色
bool pause = true;
protected Units lastTarget;//选定的地方目标
public virtual void OnInit()
{
}
public virtual void OnStart()
{
}
public virtual void OnExit()
{
}
public virtual void OnStop()
{
}
public virtual void OnDown(TriggerParamTouch param)//响应按下事件
{
}
public virtual void OnPress(TriggerParamTouch param)//响应长按
{
}
public virtual void OnUp(TriggerParamTouch param)//响应弹起
{
}
public virtual void OnMoveEnd(TriggerParamTouch param)//响应移动结束,这个消息是别人加的,暂时忽略,
{
}
public virtual void OnSkill(ITriggerDoActionParam param)//点击UI按下
{
}
public virtual void OnNavigateEnd()//寻路结束,也就是寻路到目标位置了
{
}
protected float GetSkillRange(Skill skill)//获取技能攻击范围(其他人添加,不应该放在基类中)
{
if (skill.IsAttack)
{
return self.GetAttackRange(null);
}
else
{
return skill.distance;
}
}
protected Units GetSkillTarget(Skill skill, Units enemyTarget)//获取技能目标<span style="font-family: Arial, Helvetica, sans-serif;">(其他人添加,不应该放在基类中)</span>
{
if (skill.needTarget)
{
if (enemyTarget == null)
{
if (skill.data.targetCamp == SkillTargetCamp.Self)
{
return self;
}
else if (skill.data.targetCamp == SkillTargetCamp.Partener)
{
return self;
}
else if (skill.data.targetCamp == SkillTargetCamp.AllWhitOutPartener)
{
return self;
}
}
else
{
if (skill.data.targetCamp == SkillTargetCamp.Self)
{
return self;
}
else if (skill.data.targetCamp == SkillTargetCamp.Partener)
{
if (enemyTarget.teamType == self.teamType)
{
return enemyTarget;
}
else
{
return self;
}
}
else if (skill.data.targetCamp == SkillTargetCamp.AllWhitOutPartener)
{
if (enemyTarget.teamType == self.teamType)
return self;
else
return enemyTarget;
}
}
return enemyTarget;
}
return null;
}
}
该接口响应触控事件,以及技能释放事件
3.上层组件
上层组件主要是集合所有上层操作组件(释放技能,普攻,移动)。
public class ManualController : StaticUnitComponent//该组件也是英雄角色身上的组件之一
{
#region 变量
ManualControlTarget targetCom;//目标
ManualControlSignalMng signalCom;//组件通信的信号量
List<TriggerEvent2> listTrigger2 = new List<TriggerEvent2>();//消息监听
List<ManualControllerCom> listManuControllerCom = new List<ManualControllerCom>();//组件容器
TouchEventDebug touchDebugger;//DEBUG组件
bool pause = false;//是否暂停
bool initial = false;//是否初始化
bool debugFlag = false;//是否开启debug
#endregion
public Units ControlUnit
{
get { return self; }
}
public ManualControlTarget TargetCom
{
get { return targetCom; }
}
public ManualControlSignalMng SignalCom
{
get { return signalCom; }
}
public CoroutineManager CoroutineMng
{
get { return m_CoroutineManager; }
}
#region 构造函数
public ManualController()
{
}
void Init()
{
if(debugFlag)
{
touchDebugger = new TouchEventDebug();
if(null!=touchDebugger)
{
touchDebugger.OnInit();
}
}
signalCom = new ManualControlSignalMng(this);
targetCom = new ManualControlTarget(this);
RegisterTrigger();
InvokeEveryCom("OnInit");//init组件 这是通过反射调用每个组件的对应override函数
}
#endregion
#region 事件处理
public override void OnInit()
{
base.OnInit();
if (!initial)
{
Init();
initial = true;
}
}
public override void OnStart()
{
pause = false;
base.OnStart();
InvokeEveryCom("OnStart");//组件启动
}
public override void OnStop()
{
pause = true;
base.OnStop();
InvokeEveryCom("OnStop");//停止组件
}
public override void OnExit()//退出组件
{
base.OnExit();
InvokeEveryCom("OnExit");
UnRegisterTrigger();
m_CoroutineManager.StopAllCoroutine();//停止协程
if (debugFlag && null != touchDebugger)
{
touchDebugger.OnExit();
}
}
#endregion
void OnDown(ITriggerDoActionParam param)
{
if(!CanHandleControl())//这个就是因为一些情况不需要处理,比如角色死亡之类的
return;
TriggerParamTouch info;
if (!HandleTouchParam(param,out info))//检查传入参数,传入的是个接口
return;
//if (!targetCom.UpdateTarget(info.Pos))
// return;
signalCom.Init();//初始化信号量,表示现在没有组件处理这个消息,因为每个组件依次处理,所以处理前要复位,处理过程中置位,职位后后续组件无需处理
InvokeEveryCom("OnDown", new object[] { info });
}
void OnPress(ITriggerDoActionParam param)
{
if(!CanHandleControl())
return;
TriggerParamTouch info;
if (!HandleTouchParam(param,out info))
return;
//if (!targetCom.UpdateTarget(info.Pos))
// return;
signalCom.Init();
InvokeEveryCom("OnPress", new object[] { info });
}
void OnUp(ITriggerDoActionParam param)
{
if(!CanHandleControl())
return;
TriggerParamTouch info;
if (!HandleTouchParam(param,out info))
return;
//if (!targetCom.UpdateTarget(info.Pos))
// return;
signalCom.Init();
InvokeEveryCom("OnUp", new object[] { info });
}
void OnMoveEnd(ITriggerDoActionParam param)
{
if(!CanHandleControl())
return;
TriggerParamTouch info;
if (!HandleTouchParam(param,out info))
return;
//if (!targetCom.UpdateTarget(info.Pos))
// return;
signalCom.Init();
InvokeEveryCom("OnMoveEnd", new object[] { info });
}
//@jason
void OnSkillEnd(ITriggerDoActionParam param)
{
if (!CanHandleControl())
return;
signalCom.Init();
InvokeEveryCom("OnSkillEnd", new object[] { param });
}
void OnSkill(ITriggerDoActionParam param)
{
if (!CanHandleControl())
return;
signalCom.Init();
InvokeEveryCom("OnSkill",new object[]{param});
}
void OnNavigateEnd(ITriggerDoActionParam param=null) //停止寻路回调
{
signalCom.Init();
InvokeEveryCom("OnNavigateEnd");
}
void InvokeEveryCom(string methodName,object[] param=null)//用反射调用每个组件的对应方法
{
if (string.IsNullOrEmpty(methodName))
return;
ShowEventDebug(methodName);
foreach (var it in listManuControllerCom)
{
Type t = it.GetType();
MethodInfo mInfo = t.GetMethod(methodName);
if(null!=mInfo)
{
mInfo.Invoke(it, param);
}
}
}
public void ShowEventDebug(string str)
{
if (debugFlag)
{
touchDebugger.AddEventInfo(str);
}
}
public void ShowHandlerDebug(string str)
{
if (debugFlag)
{
touchDebugger.AddHandlerInfo(str);
}
}
//---------------------------------//后面的代码就不用怎么看了,和框架没啥大关系
#region 注册回调
//切换主站英雄的时候将移动光标去掉
void RegisterTrigger()
{
RegisterEvent2(EEventID2_touch.eDown, OnDown);
RegisterEvent2(EEventID2_touch.ePress, OnPress);
RegisterEvent2(EEventID2_touch.eUp, OnUp);
RegisterEvent2(EEventID2_touch.eMoveEnd, OnMoveEnd);
RegisterEvent2(EEventID2_skillControl.ePressSkill, OnSkill);
RegisterEvent2(EEventID2_navigation.eOnStopPath, OnNavigateEnd);
//@jason
RegisterEvent2(EEventID2_skillControl.eKillEnd, OnSkillEnd);
MobaMessageManager.RegistMessage((ClientMsg)ClientV2V.BattleShop_shopOpened, OnControllerClose);
MobaMessageManager.RegistMessage((ClientMsg)ClientV2V.BattleShop_shopClosed, OnControllerOpen);
}
void UnRegisterTrigger()
{
for (int i = 0; i < listTrigger2.Count; ++i)
{
TriggerManager2.Instance.RemoveListner(listTrigger2[i]);
}
MobaMessageManager.UnRegistMessage((ClientMsg)ClientV2V.BattleShop_shopOpened, OnControllerClose);
MobaMessageManager.UnRegistMessage((ClientMsg)ClientV2V.BattleShop_shopClosed, OnControllerOpen);
}
void RegisterEvent2(EEventID2_touch eventID, Callback<ITriggerDoActionParam> fun_action)
{
TriggerCreateParamTouch param = new TriggerCreateParamTouch();
RecordTrigger(param, (int)eventID, fun_action);
}
void RegisterEvent2(EEventID2_skillControl eventID, Callback<ITriggerDoActionParam> fun_action)
{
TriggerCreateParamSkillControl param = new TriggerCreateParamSkillControl();
RecordTrigger(param, (int)eventID, fun_action);
}
void RegisterEvent2(EEventID2_navigation eventID, Callback<ITriggerDoActionParam> fun_action)
{
TriggerCreateParamNavigation param = new TriggerCreateParamNavigation();
List<TriggerCondition<ITriggerDoActionParam>> list = new List<TriggerCondition<ITriggerDoActionParam>>();
list.Add(IsPlayer);
RecordTrigger(param, (int)eventID, fun_action,list);
}
void RecordTrigger(ITriggerCreatorParam param, int eventID,
Callback<ITriggerDoActionParam> fun_action,
List<TriggerCondition<ITriggerDoActionParam>> conditions = null)
{
if (null != param)
{
param.EventID = (int)eventID;
param.TriggerID = TriggerManager2.assign_trigger_id();
param.Func_actions = fun_action;
param.Func_conditions = conditions;
TriggerEvent2 trigger = TriggerManager2.CreateTriggerEvent2(param);
listTrigger2.Add(trigger);
TriggerManager2.Instance.AddListener(trigger);
}
}
bool IsPlayer(ITriggerDoActionParam param=null)
{
TriggerParamNavigation info = param as TriggerParamNavigation;
return null != info && info.IsPlayer;
}
#endregion
bool HandleTouchParam(ITriggerDoActionParam param,out TriggerParamTouch info)
{
info = param as TriggerParamTouch;
return info != null;
}
bool CanHandleControl()
{
if (controllerUnlockTime > 0)
{
if (DateTime.Now.Ticks - controllerUnlockTime < 100 * 10000)
{
return false;
}
controllerUnlockTime = 0;
}
return null!=self && self.isLive && !self.IsLockCharaControl && !self.LockInputState && controllerLock == 0;
}
public void ShowDebugMsg(string s)
{
#if UNITY_EDITOR
ClientLogger.Debug(LogTagNames.TAG_NULL, "==>>ManualController:" + s);
#endif
}
4.组件实现
a.技能组件
每个组件不仅处理事件,而且会保存角色移动,释放技能,普通攻击的状态。以释放技能为例,其实也有很多状态,可以考虑用状态机,不过有点复杂。技能显示范围指示,技能释放过程中,技能释放结束,技能释放结束的结果,是成功,失败,还是打断,这些状态下,都可能收到对应的消息,那么要对其进行处理。如果不处理,那么信号量就是闲置,接下来普攻组件和移动组件会处理。如果处理了,后面的组件优先级较低,就不会再处理了。由于这个功能完成之后没有及时做笔记,被其他人改的面目全非,所以现在仅仅保留一个影子,可以看到一个大概。
public class ManualControlSkillNormal : ManualControllerCom
{
List<VTrigger> listTrigger = new List<VTrigger>();
ManualController controller;
ManualControlTarget targetCom;
ManualControlSignalMng signalCom;
Skill tmpLastSkill;
float tmpLastSkillTm;
Skill readySkill; //已经按出指示器,准备要放的技能
Skill lastReadySkill; //已经按出指示器,准备要放的技能
float readyTm;
void ClearReadySkill()
{
if (readySkill != null)
{
TriggerManager.Instance.SendUnitSkillPointerEvent(UnitEvent.UnitSkillCmdHidePointer, self, readySkill, Vector3.zero);
readySkill = null;
SkillView.Instance.ClearSelectState();
SkillView.Instance.HideSkillWarn();
}
}
void SetReadySkill(Skill skill, Units targetUnits)
{
ClearReadySkill();
readySkill = skill;
lastReadySkill = skill;
readyTm = Time.realtimeSinceStartup;
SkillView.Instance.SetSelectState();
SkillView.Instance.ShowSkillWarn("请选择目标");
TriggerManager.Instance.SendUnitSkillPointerEvent(UnitEvent.UnitSkillCmdShowPointer, self, readySkill, GetSkillPointerPos(skill, targetUnits));
}
void DragSkillPointer(Vector3 targetPos)
{
TriggerManager.Instance.SendUnitSkillPointerEvent(UnitEvent.UnitSkillCmdDragPointer, self, readySkill, targetPos);
}
bool ReadySkillOld()
{
return readySkill == null || Time.realtimeSinceStartup - readyTm > 0.6f;
}
#region 构造函数
public ManualControlSkillNormal(ManualController c)
{
controller = c;
self = c.ControlUnit;
targetCom = controller.TargetCom;
signalCom = controller.SignalCom;
}
#endregion
//-----------------------------------------------------------------------
#region 事件处理
public override void OnInit()
{
//YJLog.log("OnInit");
base.OnInit();
VTrigger tr = null;
tr = TriggerManager.CreateUnitEventTrigger(UnitEvent.UnitSkillOver, null, OnSkillOver, self.unique_id);
listTrigger.Add(tr);
tr = TriggerManager.CreateUnitEventTrigger(UnitEvent.UnitSkillFailed, null, OnSkillFailed, self.unique_id);
listTrigger.Add(tr);
tr = TriggerManager.CreateUnitEventTrigger(UnitEvent.UnitDeath, null, OnUnitDeath, self.unique_id);
listTrigger.Add(tr);
tr = TriggerManager.CreateUnitEventTrigger(UnitEvent.UnitSkillCmdStart, null, OnSkillStart, self.unique_id);
listTrigger.Add(tr);
//tr = TriggerManager.CreateUnitEventTrigger(UnitEvent.UnitSkillEnd, null, OnSkillEnd, self.unique_id);
//listTrigger.Add(tr);
//added by shaoming
self.ClearBornPowerObjSkillData();
//@added by shaoming
}
public override void OnStop()
{
//YJLog.log("OnStop");
base.OnStop();
targetCom.ClearSkillFlag();
targetCom.ClearMoveFlag();
}
public override void OnExit()
{
//YJLog.log("OnExit");
foreach (var it in listTrigger)
{
TriggerManager.DestroyTrigger(it);
}
base.OnExit();
}
public override void OnDown(TriggerParamTouch param)
{
//YJLog.log("OnDown");
//Debug.LogError("Control OnDown");
if (GlobalSettings.Instance.isLockView)
{
if (readySkill == null)
{
OnTrySkillOrMove(param, TouchEventType.down);
}
}
}
public override void OnPress(TriggerParamTouch param)
{
//YJLog.log("OnPress");
//Debug.LogError("Control OnPress");
if (GlobalSettings.Instance.isLockView)
{
targetCom.ClearTarget();
targetCom.UpdateTarget(param.Pos);
Vector3 targetPos = targetCom.GroundPoint;
Units targetUnits = targetCom.TargetUnit;
if (targetUnits != null && !targetUnits.CanBeSelected)
{
targetUnits = null;
}
if (!self.CanManualControl())
{
return;
}
if (readySkill != null)
{
targetCom.ClearMoveFlag();
DragSkillPointer(targetPos);
}
else
{
OnTrySkillOrMove(param, TouchEventType.press);
}
}
}
public override void OnUp(TriggerParamTouch param)
{
//YJLog.log("OnUp");
//Debug.LogError("Control OnUp");
if (GlobalSettings.Instance.isLockView)
{
if (readySkill != null)
OnTrySkillOrMove(param, TouchEventType.up);
}
else
{
OnTrySkillOrMove(param, TouchEventType.up);
}
}
public virtual void OnMoveEnd(TriggerParamTouch param)
{
//YJLog.log("OnMoveEnd");
//Debug.LogError("Control OnMoveEnd");
if (GlobalSettings.Instance.isLockView)
{//判断移动 或者 攻击
if (readySkill != null)
{
OnTrySkillOrMove(param, TouchEventType.moveEnd);
}
else
{
targetCom.ClearTarget();
targetCom.UpdateTarget(param.Pos);
Units targetUnits = targetCom.TargetUnit;
if (targetUnits != null && !targetUnits.CanBeSelected)
{
targetUnits = null;
}
if (!self.CanManualControl())
{
return;
}
if (targetUnits == null)
{
OnTrySkillOrMove(param, TouchEventType.moveEnd);
}
}
}
else
{//可以滑动视窗时,什么都不操作
}
}
void OnTrySkillOrMove(TriggerParamTouch param, TouchEventType type)
{
targetCom.ClearTarget();
targetCom.UpdateTarget(param.Pos);
Vector3 targetPos = targetCom.GroundPoint;
Units targetUnits = targetCom.TargetUnit;
bool needForceSend = type == TouchEventType.press ? false : true;
if (targetUnits != null && !targetUnits.CanBeSelected)
{
targetUnits = null;
}
if (!self.CanManualControl())
{
return;
}
if (readySkill == null)
{
if (targetUnits == null)
{
DisableAIAutoAttackMove();
targetCom.DrawMoveFlag();
self.mCmdCacheController.EnqueueMoveCmd(targetPos, needForceSend);
lastTarget = null;
}
else
{
if (type == TouchEventType.down || (type != TouchEventType.down && lastTarget != targetUnits))
{
lastTarget = targetUnits;
DisableAIAutoAttackMove();
targetCom.ClearMoveFlag();
self.mCmdCacheController.EnqueueSkillCmd(self.attacks[0], targetPos, targetUnits);
}
}
}
else
{
targetCom.ClearTarget();
targetCom.UpdateTargetAll(param.Pos, readySkill.data.targetCamp);
targetCom.ClearMoveFlag();
DragSkillPointer(targetCom.GroundPoint);
LaunchSkill(readySkill, targetCom.TargetUnit, targetCom.GroundPoint);
ClearReadySkill();
lastTarget = null;
}
}
void OnSkillOver()
{
}
void OnSkillFailed()
{
}
void OnUnitDeath()
{
//YJLog.log("OnUnitDeath");
ClearReadySkill();
}
void OnSkillStart()
{
Skill skill = self.GetSkillContext();
if (skill != null && skill == readySkill)
{
ClearReadySkill();
}
}
public override void OnSkill(ITriggerDoActionParam param)
{
TriggerParamSkillControl info = param as TriggerParamSkillControl;
//YJLog.log("OnSkill:" + info.SkillID);
Skill skill = self.getSkillOrAttackById(info.SkillID);
Units targetUnits = FindTargetHelper.FindAutoSkillTarget(self, self.trans.position, TargetTag.Hero, self.atk_type == 1 ? 6.5f : GetSkillRange(skill) + 2);
if(targetUnits == null)
targetUnits = PlayerControlMgr.Instance.GetSelectedTarget();
targetCom.ClearMoveFlag();
if (!self.CanManualControl())
{
return;
}
if (self.IsSkillCanTriggerBornPowerObj(info.SkillID))
{
ClearReadySkill();
LaunchSkill(skill, targetUnits);
return;
}
if (DoseSkillNeedDoubleClick(skill))
{
if (skill == readySkill)
{
if (ReadySkillOld())//过期时 再次点击是清楚上次选择
{
readyTm = Time.realtimeSinceStartup;
ClearReadySkill();
}
else
{
ClearReadySkill();
LaunchSkill(skill, targetUnits);
}
return;
}
if (readySkill == null && lastReadySkill == skill && Time.realtimeSinceStartup - readyTm < 0.5f)
{
ClearReadySkill();
LaunchSkill(skill, targetUnits);
return;
}
SetReadySkill(skill, targetUnits);
return;
}
else
{
ClearReadySkill();
LaunchSkill(skill, targetUnits);
}
}
public override void OnNavigateEnd()
{
//YJLog.log("OnNavigateEnd");
self.mCmdRunningController.OnMoveEnd();
targetCom.ClearMoveFlag();
}
#endregion
Vector3 GetSkillPointerPos(Skill skill, Units targetUnits)
{
if (targetUnits != null)
return targetUnits.mTransform.position;
else
{
return self.mTransform.position + self.mTransform.forward * 10f;
}
}
void LaunchSkill(Skill skill, Units targetUnits)
{
Vector3 targetPos = Vector3.zero;
if (!skill.needTarget)
{
if (targetUnits != null && !targetUnits.CanBeSelected)
{
targetUnits = null;
}
if (targetUnits == self)
{
targetUnits = null;
}
targetPos = self.mTransform.forward;
if (targetUnits != null)
{
targetPos = targetUnits.mTransform.position;
}
else
{
targetPos.Normalize();
if (skill.distance == 0)
targetPos = self.mTransform.position + targetPos;
else
targetPos = self.mTransform.position + targetPos * skill.distance;
}
LaunchSkill(skill, targetUnits, targetPos);
}
else
{
targetUnits = GetSkillTarget(skill, targetUnits);
if (targetUnits != null)
{
LaunchSkill(skill, targetUnits, targetPos);
}
else
{
UIMessageBox.ShowMessage("未选择目标,不能释放!");
}
}
}
void LaunchSkill(Skill skill, Units targetUnits, Vector3 targetPos)
{
if (skill.needTarget)
{
targetUnits = GetSkillTarget(skill, targetUnits);
if (targetUnits != null && !targetUnits.CanBeSelected)
{
targetUnits = null;
}
if (targetUnits != null)
{
DisableAIAutoAttackMove();
targetCom.ClearMoveFlag();
self.mCmdCacheController.EnqueueSkillCmd(skill.realSkillMainId, targetUnits.mTransform.position, targetUnits);
}
else
{//如果指向性技能没选择目标 该怎么默认处理
}
}
else
{
if (targetUnits != null && !targetUnits.CanBeSelected)
{
targetUnits = null;
}
if (targetUnits == self)
{
targetUnits = null;
}
DisableAIAutoAttackMove();
targetCom.ClearMoveFlag();
self.mCmdCacheController.EnqueueSkillCmd(skill.realSkillMainId, targetPos, targetUnits);
}
}
bool DoseSkillNeedDoubleClick(Skill skill)
{
return !skill.IsInstance;
}
void DisableAIAutoAttackMove()
{
if (self.aiManager != null)
{
self.aiManager.EnableSearchTarget(false);
}
self.SetAIAutoAttackMove(false);
}
}