1.设置环境光
2.搭建客户端框架
a.对上述的模块基类(都是单例类,都有初始化方法)
b.隐藏登录页面(得到Canvas的子对象失活)
c.设置根对象过场景不被移除
tip:模块都是继承mono的,不能直接new出来使用,通过挂载对象,得到脚本组件来使用。
3.登录UI
a.声明LoginWnd 继承公共类WindowRoot
b.LoginSys调用相关的登录方法
4.添加登录界面音效
a.资源加载音效文件返回给音效模块使用
b.创建了一个系统基类,系统子类重写初始化方法
c.LoginSys调用相关的加载音乐方法
5.开始按钮的点击
6.设置tips界面
a.动画实现淡入淡出效果
b.避免多个tip混用,用队列实现一条tip实现
c.一条tip显示完之后,bool标志设为false。
d.多个模块都有tips,在根物体下声明tip类(UI模块都继承自Windowroot,其具有对根物体的引用,其他UI模块继承就直接可以用增加tip方法了),并定义增加tip的方法
************************客户端框架结束**********************
7.搭建服务端框架
8.引入KCP.dll
MobaSession 会话类 MobaProdof协议类
9.对网络消息的处理
服务器的IO操作很密集,要服务多个客户端,要使用多线程,收到数据之后,数据不能都在多线程接中处理,有些核心敏感的数据必须放在单线程中处理(比如两个客户端在同一时间在多线程里收到了请求登录的消息:一样的账号和密码,只能一个客户端登录,这时候服务器是不能让这两台客户端同时上线的,先来的先登录,提示后来的此账号已登录),保证核心数据的可靠性。
要实现上述,有两种方法:
1.接收到数据之后就进行加锁处理,逻辑部分就是在单线程中处理的。较简单。
2.接收数据还是在多线程中处理,最后拿数据的时候再进行单线程处理,确保数据的安全,较复杂,很多的加锁解锁
这里用的是第一种:声明一个字符串,锁这个字符串(就是锁一个固定的内存地址),用队列来实现消息的进出。
10.客户端和服务端成功建立连接
11.服务器处理分发消息
网络模块 NetSvc:HandleMsg分发给 登录系统 LoginSys
LoginSys 进行判定是否已经上线(依赖网络模块 CacheSvc)最后存储相应数据到 CacheSvc
12.客户端处理消息回应
NetSvc得到消息进行判断 调用登录系统的登录方法(保存用户数据)
用户数据写在GamRoot里 方便调用
Start界面的显示 按钮绑定 Img进度条的设置:
13.匹配计时界面的显示
14.请求匹配队列
框架思路:
a.定义Msg协议类(协议序列化类,匹配类型,声明类) 搞完之后要重新生成一下 再进unity才能使用
b.客户端点击相应的匹配按钮 发送相应的协议类消息给服务端
服务端对这些消息进行分发处理,Session接收消息放进消息队列 update里面自动调用HandleMsg方法
c.匹配系统
15.匹配倒计时(TickTimer)
写在倒计时服务模块
参数0表示需要使用内部封装的update来驱动,其他数字代表新开线程来驱动。false表示不需要放到其他处理器字典进行处理,直接在update里面处理。
16.客户端跳转匹配确认界面
固定模式:UI模块 服务模块处理系统模块的函数 系统模块对应相应的UI处理函数
确认界面UI绑定 、音效、倒计时
17.确认信息同步到各个客户端
客户端发来确定信息=>服务端接收=>相应的系统处理(RoomSys)=>相应的房间根据session处理得到确认的id=>相应的房间状态(RoomStateConfirm)根据id进行处理:广播消息
18.服务端处理英雄确认消息
客户端发来英雄确定信息=>服务端接收=>相应的系统处理(RoomSys)=>房间(pvproom)根据session处理得到确认的id=>得到相应的房间状态(RoomStateSelect)根据id进行处理
战斗逻辑
1.初始化碰撞环境
2.声明框架类
3.客户端发送移动数据到服务器
4.服务端对移动操作的下发
战斗系统收到服务端的移动操作信息后,调用战斗管理类的输入方法,接着调用主要逻辑类的输入方法,判断是什么类型的操作,进入相应的移动,技能等子模块,子模块进行相应的处理。
5.运动预测算法逻辑 (69)
基于上一个逻辑帧的LogicPos进行预测,具体是 viewtarget +=方向*速度*Time.deltime。等到下一次逻辑帧的数据发过来就强制刷新到逻辑帧的位置。
同时,如果英雄转弯,直接把预测的位置赋值给position会导致一卡一卡的。平滑处理:调用V3.lerp函数,第三个参数是时间*平滑的加速度
转向预测平滑:
6.运动朝向的平滑处理
因为不知道下一步玩家会向哪里转,不适合做预测处理,但是可以优化,具体步骤是:声明一个基于角度大小的一个变化量 ,转的角度越大变化就越快。
void UpdateDirection()
{
if (logicUnit.isDirChanged)
{
viewTargetDir = GetUnitViewDir();
logicUnit.isDirChanged = false;
}
//转向平滑处理
if(SmoothDir)
{
float threshold=Time.deltaTime*viewDirAccer;
float angle=Vector3.Angle(RotationRoot.forward, viewTargetDir);
//基于角度大小的一个变化量 越大变化量就越大
float angleMult=(angle/180)*AngleMultipLier*Time.deltaTime;
if(viewTargetDir!=Vector3.zero)
{
Vector3 interDir=Vector3.Lerp(RotationRoot.forward, viewTargetDir,threshold+ angleMult);
RotationRoot.rotation=CalcRotation(interDir);
}
}
//强转
else
{
RotationRoot.rotation = CalcRotation(viewTargetDir);
}
7.玩家碰撞墙体后的朝向处理
Hero类重写方法,与墙体碰撞后返回的是玩家UI输入的朝向。
8.添加普攻音效
在skillcfg里添加三种音效=>ResSkillCfgs里去指定三种音效的路径=>AudioSvc(延迟)播放音效
ViewUnit类(PlayAudio(AudioSvc(延迟)播放音效))=> MainLogicUnit 调用mainViewUnit的(PlayAudio(AudioSvc(延迟)播放音效))
释放技能函数=>施法前摇函数(设置技能状态,播放音效)
9.飘字显示
MainLogicAttrs(飘字信息:hp;受到伤害之后的回调委托)=>MainViewUnit(实现了回调委托,更新了JumpUpdateInfo的3d信息位置)=>HpWnd(调用JumpNum的show方法,显示飘字效果)
基本就是逻辑层做委托回调,显示层更新位置信息,UI层进行显示处理。
10.技能计时器
一些参数:
驱动计时器TickTimer 运行:
延迟计时的倒计时,多余的时间放入Tick中处理;得到 delay循环的进度和总体循环进度,调用,延迟计时结束后调用Tick。
Tick 进行:
计时按钮案例
//test
MonoTimer timer;
public void ClickTime()
{
SetText(txt, 5);
timer?.DisableTimer();
timer = CreateMonoTimer(
//循环次数 设置时间
// MonoTimer里面默认构造loopCount等于1
(loopCounter) =>
{
this.Log("loop:" + loopCounter);
SetText(txt, 5 - loopCounter);
},
1000,
5,
(isDelay, prgloop, prgall) =>
{
//每次循环 imgloop显示
SetActive(imgloop, true);
//是否延迟 数字是否显示
if (isDelay)
{
SetActive(txt, false);
}
else
{
SetActive(txt, true);
}
//更新两者的fillAmount
imgloop.fillAmount = 1 - prgloop;
imgAll.fillAmount = prgall;
},
//结束置空
() =>
{
SetActive(imgloop, false);
imgAll.fillAmount = 0;
this.ColorLog(PEUtils.LogColor.Green, "Loop End");
}
);
}
逻辑定时
哪些地方用到了逻辑定时?
1.上述的技能逻辑定时(技能延迟执行)
2.小兵出生的逻辑定时
一波里面的小兵出生计时
每波小兵的计时
3.金币的逻辑计时
以上除了技能逻辑定时是在对应的逻辑技能类中,其余都是在FigMgr的Tick里进行tick(都放入一个List)
11.亚瑟一技能的强化普攻处理(127)
技能本质上就是通过配置信息来起作用的,所以通过改变cfg的信息就可用实现不同的强化技能效果。
思路主要是用另外一个技能替换掉普攻,在MainSkillLogic中声明一个得到技能的方法(GetSkillbyID)并返回技能,skill类的技能替换函数调用GetSkillConfigByID函数判断是否是普攻进行cfg信息的替换,替换buff在相应的类中进行处理。
skill类初始化的时候通过skillid加载不同的cfg配置文件,就会改变cfg的信息
替换buff的两种结束状态:1.强化普攻释放完毕(Start函数),cfg信息变为原来的(End函数)。 2.3s内没有进行普攻,也变为原来的cfg信息
12.移动下普攻不显示动画,朝向没有改变的问题
移动下普攻不显示动画:free Ani替代了atk Ani,在MainlogicView增加是否处于技能前摇的判定
朝向没有改变:HeroView的更新朝向都是UI输入方向,加个判定,如果处于技能前摇阶段就返回的是ViewTargetDir
13.释放技能时按方向键产生滑动;技能释放完之后方向键不松也会产生滑动idle。
释放技能时按方向键产生滑动:在MainLogicMove里增加判定:存在技能释放前摇的时候,此时玩家的位置不被ui输入所影响。
技能释放完之后方向键不松也会产生滑动idle:原因是技能释放完成后会调用相关的回调(end状态播放了free Ani)。技能释放完之后方向键不松:前摇结束了,期间一直有ui的输入,所以技能后摇没有,直接结束,但技能SkillTalTime还在计时,计时完毕就调用关的回调(end状态播放了free Ani);
解决办法:在LogicUnit声明一个回调(UI输入改变),在LogicPos属性中set里进行调用。在MainLogicSkill里的初始化函数中进行回调的绑定;
14.亚瑟一技能标记加伤buff的实现
在声明的buff类中的Start和End分别调用,Onhurt这一受到伤害的回调,+= ,-=。
15.亚瑟一技能友军群体加速buff
注意点:tick中先恢复速度,再次进行targetList查找。
16.亚瑟大招跳跃向敌人buff
17.亚瑟大招击飞buffCfg
18.亚瑟大招持续伤害buff
15.智能移动攻击完之后玩家一直在移动;智能移动攻击的优先级大于UI输入
解决办法:1.在智能移动攻击完之后让玩家停止(BattleSys.instance.SendMoveKey(PEVector3.zero);)
2.playwnd判断是否有UI输入,BattleSys声明对应的函数。在智能移动攻击的buff里发送移动消息之前先判定是否有ui输入,存在就把buff状态置为None。
亚瑟普攻和一技能制作总结
技能释放流程:客户端NetSvc接收到NtfOpKey消息=>BattleSys的 NtfOpKey(MobaMsg msg)=>FIghtMgr中接收Battlesys的消息=>下发到MainLogicUnit=>根据消息类型(skill)对应的逻辑类(MainLogicSkill)进行处理(根据传来的技能消息包含的id和技能数组id比较)=>根据消息指定级技能的范围+Skill类进行释放技能的具体处理
buff释放流程:
三地方创建buff或者是附加buff
一个是在MainLogicSkill的Init里面:创建被动buff
其余都是在Skill里的释放技能的时候:根据技能的释放情况创建hit之后产生的buff、技能释放后附加到自身的buff
移动、计时和buff的驱动:BattleSys驱动fightMgr=>fihtMgr驱动对应的英雄模块=>Hero模块Tick(没有实现,执行的是父类MainLogicUnit的LogicTick)=>MainLogicUnit的LogicTick(包括移动的驱动和技能的驱动:技能驱动又包括:buff驱动和计时驱动)
Skill类进行释放技能的具体处理:对应的流程在技能运行框架图结合代码。
亚瑟开发结束
****************************
后羿开发开始
后羿有七个技能:(括号是对应的buff)常规的分别是:普攻,一(10201,这个buff只是用来替换技能的,没有实质的效果)二( 10220, 10221, 10222, 10223)三技能(10230, 10231)
一技能强化普攻为散射替换技能1024(10240)
被动多重设计替换技能1025(10250)
一技能强化和被动强化结合替换技能1026(10260):这个buff的代码就是10240和10250的结合体。
二技能的中间额外伤害和减速是通过得到技能释放的LogicPos,根据LogicPos,再通过设置的buff的range不一样实现的。
1.子弹体积算法
2.驱动子弹
ResSvc 创建子弹函数(返回相应的子弹类型)=>FightMgr tick子弹(放到战斗管理器是因为要不受其他单元影响,独立),battleSys添加Add方法=>MainLogicSkill类创建具体的子弹,并初始化,放入List=>Skill技能生效函数,得到创建的子弹,设置回调。
3.后羿被动加攻速buff的实现
三件套:buff类,buff配置,ResSvc加载
buff类的具体逻辑:
初始化:各参数赋值,得到所有技能,把释放完的回调绑定声明的OnSpellSkillSucc函数。
OnSpellSkillSucc函数:计时和重置 ,调用ModifyAttackSpeed直接改变攻击速率。
Tick函数:每66ms检测一次
ResetSpeed重置函数:还原速率
3.被动的替换普攻buff的实现
三件套:buff类,buff配置,ResSvc加载
buff类的具体逻辑:
初始化:各参数赋值,得到所有技能,把释放完的回调绑定声明的OnSpellSkillSucc函数。
OnSpellSkillSucc函数:计时和重置 ,调用ReplaceSkillCfg直接替换技能。(注意是否提前按下了一技能)
ResetSpeed重置函数:还原技能
3. 一技能强化普攻为散射替换技能1024(10240)
4.后羿二技能配置
技能没有设置,在buff里造成伤害
4.实现后羿一技能的曲线效果,设置子弹曲线弹道。
1.设置一个随机数种子
红绿两个向量合成为黄色。
绿色的向量就是垂直于红色向量和upx向量所构成的平面=>(所以可以直接用叉乘得到)
最后绿色的向量可以向上偏移随机正数个位置。(美术效果更好)
5.报错:在播放动画的时候有数值<0;
在修改攻击速率的时候,直接赋值了变量而不是其属性 导致属性里set的逻辑没执行。
技能替换后,时间还是原来的值不用改变。
6.伤害飘字的优化
beforre:飘字重叠,不太美观
优化思路:把相关的飘字和血条结合在一起,伤害飘字的jump放到ItemHp类中的一个队列里面,出一个显示一个
相关的性能优化:取消了ItemHp里的生命周期Update,优化到了HpWnd里的Udate,判断DicItem里是否有东西才去调用,提升性能。
7.小地图制作思路
把对应地图上的英雄位置减去原点位置得到的v3赋值给小地图的RectTransForm的localPosition(进行了一定的缩放 scaler)
8.小兵的AI实现
通过InputMoveForwarKey指定小兵的移动方向。
每五帧检测一次,是否有目标可以攻击,遇到敌方就攻击,若找不到目标,则启用智能攻击。若任然超出智能攻击范围,就继续执行InputMoveForwarKey指定的方向。
9.本地模式(模拟服务端)的实现流程(拦截消息)
主要就是playWnd里的移动消息发送(playWnd)和技能消息发送(SkiItem)
BattleSys发送消息
GM模式拦截 return
在FixedUpdate里根据66ms的频率(手动设置)来tick整个逻辑帧。
在66ms内收到的所有操作数据,全部广播发送给客户端。
加入队列之后,update判断是否是GM模式,处理消息
10.再写服务端~
服务端处理操作消息流程:NetSvc接收消息=>RoomSys判断消息属于的房间=>pvpRoom判断房间状态=>RoomStateFight类进行处理
NetSvc接收消息
RoomSys判断消息属于的房间
pvpRoom判断房间状态
RoomStateFight类进行处理
11.聊天相关
服务端:定义相应的协议,最后到pvproom分发给各客户端
客户端:最后到PlayWnd处理
三个函数:
AddMsgContent:添加相应的聊天消息到数组中;超过消息最大数量就减去第一个
RefreshChatUI:刷新txt的UI显示,每条消息后加\n换行
UpdateChatList:驱动聊天文字显示时间
12.网络延迟,心跳消息相关
客户端updete里用Invoke发送ping消息给服务端
在Init里重置的时候(把消息队列给清空)取消掉invoke,不取消的话就会有很多个NetSvc的invoke发送消息
服务端定义对应的ping协议
接收客户端session发来的消息
根据pingID马上回复RspPing消息给对应的客户端
51:小兵逻辑
小兵的移动用的是InputFakeMoveKey直接传入移动的方向
tick里调用。
52.塔的逻辑
初始化判断水晶掉没掉,执行相应的代码
tick里调用AItick
遇到的问题:
1.
【Unity 导入项目时Resolving Packages卡住的解决方案】_resolving packages卡住了-CSDN博客
2.继承接口的类赋值给对应的接口对象
3.
4.序列化和反序列化的c#封装方法 压缩和反压缩
5.序列化
Unity基础篇:Serializable总结与深入研究。_unity serializable-CSDN博客
6.unity报空 结果进代码调式第一次都有值,需要一直点调试,直到第二次发现消息为空!找了三四个小时 0.0 发现是消息发错了 一个消息发了两遍 !!
7. float转换会损失精度 必须用显示转换
8.c#implicit和explicit的区别
定点数运算课程:
8.技能拖拽位置一直不对,钳制在Canvas的左下角,是因为把localPosition错误设置成了Position!!!
9.传的是id而不是index
10.vs代码不能挂载到物体上,原因是因为vs脚本名和unity声明的脚本名不一致导致的!!
11.亚瑟二技能对亚瑟单位伤害很高,2s直接死亡,原因是二技能buff配置中的damage设置成了
int 解决办法:改成PEInt就没问题了
12.亚瑟大招过后立马按平A,平A的动画没有播放完就进入了free状态(其实是大招后摇没结束被打断所调用的free)。(每个技能结束后都会调用free)
解决办法:在调用free之前先清空free调用。
5.实现后羿三技能碰撞伤害时,传友军的问题
这个buff实际上是创建dir子弹,命中敌人所导致的。注意最后一句代码,是target创建的buff,即命中的敌人它自己创建的buff,所以上述buff配置中,作用的范围应该是敌方的友军。
类比 目标子弹 TODO
6.水晶爆炸,结束界面弹出时报错:SkillItem里的协程无法启动。
原因:游戏结束时,playwnd关闭,resWnd开启,这样playwnd里的effectRoot结点就看不到了。但是还是处于激活状态(所以这里判断只能用activeInHierarchy(场景中是否显示),而不是activeSelf(物体是否显示)
因为playwnd关闭,其下的所有子物体都是关闭的,但是落实到子物体可能还是处于激活状态);
解决办法:加上是否在场景中显示的判断。
7.水晶爆炸后点击继续按钮无法返回到大厅
原因:pvproom里面忘记把end状态添加到相应字典了。房间进入end状态广播消息给客户端是在Enter函数中而不是构造函数中。
8.水晶爆炸后,回到大厅再次匹配,进入英雄选择界面时,左侧有三个角色,两个后羿一个亚瑟。
原因:采用的直接遍历,没有清除干净。
解决:采用倒叙遍历清除scroll的内容
9.一直点普攻英雄就会一直攻击(频率很快就很恐怖,和实际游戏不一样)
原因:判断是否释放完技能函数那里有错误 写快了
10.遥感抬起后,英雄一直移动,没有停止
参数传错了!!!! 浪费好多时间
11.玩家遥感移动过程中释放技能完毕后,玩家任然朝原方向移动
原因:技能前摇阶段的v3.zero消息没有发送出去,导致技能释放完毕后,执行的移动还是释放技能之前的遥感输入
解决:取消这个判断
12.基于上一个问题解决后,移动是可以停止了,但是技能释放完之后不能播放到free动画
原因:技能前摇阶段发送了v3.zero的消息,而ui输入改变的时候就会触发回调,把free动画清理掉。
解决:加上判定,不是前摇阶段才清空。
13.低帧率下英雄血条抖动问题
原因:相机的位置更新快于血条的相关位置的映射
解决:把相机的位置更新从update改为放到lateupdate中
14.英雄特定方向行走(z轴负方向)模型颠倒问题
原因:四元数的计算出现了问题,
解决:不使用四元数来计算,之间把得到的朝向赋值给物体的forward即可
之前:
改为:
15.【修正】高帧率下高频输入延时问题
一个逻辑帧只能跑15次,每秒15帧
优化:1.服务端方面 对消息的处理是if判断 也就是说一个逻辑帧只能执行15次ui信息输入,改成while判断
2.playwnd发送遥感消息方面,用FixedUpdate,设置每秒中发送消息的次数的最大值