【笔记】unity逻辑类各种即使方案汇总

迷雾遮罩:
1.使用war3的地图拼接
2.改变网格范围,传到shader中

寻路:
需要高精度的则不适合用Navmesh

1.https://howtorts.github.io/2014/01/04/basic-flow-fields.html
基于流场,估计类似sdf,使用Dijkstra生成路径

2.rts寻路
A Start Pathfinding Project Pro + Unity NavMesh
寻路算法+精确碰撞(以及避碰)

3.多人寻路中的交互避碰
RVO2

4.定时器,当有大量定时器时
时间轮
分级时间轮

5.mmo同步位置,预测拉扯
#客户端A在p1发起移动的包里有客户端和服务端的时差和移动方向和速度以及目标点p4, 服务端接收到包后跟距时差T1和T3估算出客户端当前位置(可能会需要校验,矫正)
再讲这个位置下发给所有客户端,其他客户端接收到数据包后再根据T4和T3的时差去预测客户端A当前位置p3,用以下公式计算速度S,插值移动到p4
S = (P4-P1)/(P4-P3)
客户端A在发起移动消息时,本地生成队列,接收到校验通过的消息时移除,校验失败则回扯到p1

#用于同步位置的协议
Msg_PlayerMove                            //客户端向服务端发送玩家移动
(a)MSG_PlayerMove_Pos           //客户端向服务端发送移动中玩家位置改变
(b)MSG_PlayerMove_Pos_Rotate, //客户端向服务端发送移动中玩家位置和朝向改变
(c)MSG_PlayerMove_StopPos,                //服务端通知客户端原地不动玩家的位置消息(移动到目标点后)
(d)MSG_PlayerMove_StopPos_Rotate,        //服务端通知客户端原地不动玩家的位置和朝向消息(移动到目标点后)

#角色移动同步,位置同步
服务端做九宫格,九宫格一个格子比手机的一半大一点
客户单移动时每移动一定间隔,发一次位置到服务端,服务端判断是否跨越了大格子,跨越了就发跨越消息包到客户端,否则不进行广播
客户端状态变化很快时,比如快死时玩家一直点地图,当2次状态变化之间小于阈值时,启用一个消息缓存队列,延迟发送有效数据(接到消息后再发下一个)

#怪物的随机移动不同步

#减少怪物攻击同步
怪物可以每进行一次攻击,客户端就发一个消息给服务器。这样做,消息还是有点多,特别是一群怪围着几个角色进行攻击时,消息广播还是有点多。
所以可以将状态的概念向上扩大,只同步怪物在攻击哪个玩家,而不同步每一次的攻击,然后由每个客户端根据怪物固定的攻击速度各自去表现。这样一个怪去攻击一个玩家,就会只有一次消息广播了。

6.技能系统设计
一个技能由n个效果组成,程序维护效果,通过编辑器看表现
解耦逻辑与显示

7.Buff机制设计
回调点机制:BuffOccur(添加),BuffOnTick(间隔触发),BuffRemoved(移除),BuffBeHurt(在受到攻击的时候触发,已确定被打中,盾类技能受到攻击)
BuffOnHit(被攻击,未确定是否会被击中,以及未确定伤害是多少,可传参isHit像isCrit),BuffBeforeKilled(杀死buff附着者前),BuffAfterKilled(杀死buff附着者后)

8.反外挂
限制客户端发送移动消息的频率
移动距离检测
消息时间检测
1天内经验和金币增加数量限制
抽查

9.万象锁根源
x,z轴绕模型坐标系旋转,y轴绕惯性坐标系旋转,所以会出现,y和z轴共旋转面的情况

10.mmorpg Animator使用(大量动作文件时)
# 使用Mecanim Control插件,使用方法类似Play CrossFade,缺点不支持BlendTree
#代码生成AnimatorController,同时类似于多个idle动画到技能的切换,可做成在状态机A(包含n个idle)到状态机B的切换
#多个idle到多个技能的切换,可做成运行时动态替换idle动画

11.帧同步平滑位移
服务端为主,缺点为启动重,停止时刹不住
影子追随算法

12.处理网络抖动
插值逼近
预备动作
引入硬直
丢包处理(UDP丢包章节介绍)
对时、对帧

13.状态同步经验
 server 对攻击目标的位置做估算的时候,可以不按上次发出包的运动方向去做位置估计,而选择用最有利于被攻击者的运动方向来做。这样,可以减少网络状况差的玩家的劣势。
 对于 PVE 的战斗,甚至可以做更多的取舍,达到游戏流畅的效果。比如一个网络状态差的玩家去打 npc,他攻击 npc 的时刻,npc 是处于攻击范围之内的。但是由于网络延迟,数据包被 server 收到的时候,npc 已经离开。这个时候 server 可以判定为miss

14.时间校准
1.客户端发给服务端 时间戳 t1
2.服务端接收到 时间戳 t2
3.服务端发给客户端 时间戳 t3
4.客户端接收到 时间戳 t4
网络来回总延时 delay = (t4 - t1) - (t3-t2) //t3-t2为服务器内部耗时
客户端与服务端时间差 delta = ( (t2-t1) + (t4-t3) - delay ) / 2
delay1 为 t1 至 t2 延时,delay2 为 t3至t4延时
t2 - t1 即 delta + delay1
t4 - t3 即 delta + delay2
delay = delay1 + delay2

15.定点数的物理模拟
有隐患:只使用unity的射线0,BoxCollider,尾数截断,按碰撞方向截断,只使用Navmesh计算出的路点(估计只能有一个客户端作为决定源)

16.帧同步针对延迟的优化
逻辑层和表现层完全分离
不使用FixedUpdate的方式降低输出延迟
使用了TCP,自己实现的可靠UDP和冗余包的非可靠UDP三种方式通信;
极致压缩优化协议包降低在MTU范围以内;逻辑帧负载均衡;分地域部署和匹配;多线程收发

17.可能引起不同步的:
不同的调用顺序,时序,浮点数计算的偏差,容器的排序不确定性,Coroutine内写逻辑带来的不确定性,
物理浮点数,随机数值带来的不确定性
逻辑到表现的通知不能用事件,因为回滚的话会漏掉事件的触发

18.帧同步同步
可靠传输的UDP,冗余信息的不可靠UDP(最好使用这个方案)
#基于可靠传输的UDP,是指在UDP上加一层封装,自己去实现丢包处理,消息序列,重传等类似TCP的消息处理方式,保证上层逻辑在处理数据包的时候,不需要考虑包的顺序,丢包等。类似的实现有Enet,KCP等。
#冗余信息的UDP,是指需要上层逻辑自己处理丢包,乱序,重传等问题,底层直接用原始的UDP,或者用类似Enet的Unsequenced模式。常见的处理方式,就是两端的消息里面,带有确认帧信息,比如客户端(C)通知服务器(S)第100帧的数据,S收到后通知C,已收到C的第100帧,如果C一直没收到S的通知(丢包,乱序等原因),就会继续发送第100帧的数据给S,直到收到S的确认信息。

19.帧同步下的动作切换,技能播放
时间轮+从动画片段抽象出的逻辑帧(可与数据核心的帧率结合,不能依赖于Animator播放的时间,或者AnimatorStateInfo的NormalizedTime)

20.帧同步逻辑层面的预测
http://dy.163.com/v2/article/detail/DN3MIAE00511L9VL.html
客户端领先服务端的帧数,网络好的领先的少,大概2帧,网络差的领先的多,大概5帧 
当前客户端(A,B)执行到100帧,服务器执行到97帧。在100帧的时候,A执行了移动,B执行了攻击,A和B都通知服务器:我已经执行到100帧,我的操作是移动(A),攻击(B)。服务器在自己的98帧或99帧收到了A,B的消息,存在对应帧的操作数据中,等服务器执行到100帧的时候(或提前),将这个数据广播给AB。
  然后A和B立刻开始执行100帧,A执行移动,预测B不执行操作。而B执行攻击,预测A执行攻击(可能A的99帧也是攻击),A和B各自预测对方的操作。
  在A和B执行完100帧后,他们会各自保存100帧的状态快照,以及100帧各自的操作(包括预测的操作),以备万一预测错误,做逻辑回滚。
  执行几帧后,A和B来到了103帧,服务器到了100帧,他开始广播数据给AB,在一定延迟后,AB收到了服务器确认的100帧的数据,这时候,AB可能已经执行到104了。A和B各自去核对服务器的数据和自己预测的数据是否相同。例如A核对后,100帧的操作,和自己预测的一样,A不做任何处理,继续往前。而B核对后,发现在100帧,B对A的预测,和服务器确认的A的操作,是不一样的(B预测的是攻击,而实际A的操作是移动),B就回滚到上一个确认一样的帧,即99帧,然后根据确认的100帧操作去执行100帧,然后快速执行101~103的帧逻辑,之后继续执行104帧,其中(101~104)还是预测的逻辑帧。
  因为客户端对当前操作的立刻执行,这个操作手感,是完全和PVE(不联网状态)是一样的,不存在任何Delay。所以,能做到绝佳的操作手感。当预测不一样的时候,做逻辑回滚,快速追回当前操作。
  这样,对于网络好的玩家,和网络不好的玩家,都不会互相影响,不会像Lockstep一样,网络好的玩家,会被网络不好的玩家Lock住。也不会被网络延迟Lock住,客户端可以一直往前预测。
  对于网络好的玩家(A),可以动态调整(根据动态的Latency),让客户端领先服务器少一些,尽量减少预测量,就会尽量减少回滚,例如网络好的,可能客户端只领先2~3帧。
  对于网络不好的玩家(B),动态调整,领先服务器多一些,根据Latency调整,例如领先5帧。
  那么,A可能预测错的情况,只有2~3帧,而网络不好的B,可能预测错误的帧有5帧。通过优化的预测技术,和消息通知的优化,可以进一步减少A和B的预测错误率。对于A而言,战斗是顺畅的,手感很好,少数情况的回滚,优化好了,并不会带来卡顿和延迟感。
  重点优化的是B,即网络不好的玩家,他的操作体验。因为客户端不等待服务器确认,就执行操作,所以B的操作手感,和A是一致的,区别只在于,B因为延迟,预测了比较多的帧,可能导致预测错,回滚会多一些。比如按照B的预测,应该在100帧击中A,但是因为预测错误A的操作,回滚重新执行后,B可能在100帧不会击中A。这对于B来说,通过插值和一些平滑方式,B的感受是不会有太大区别的,因为B看自己,操作自己都是及时反馈的,他感觉自己是平滑的。
  这种方式,保证了网络不好的B的操作手感,和A一致。回滚导致的一些轻微的抖动,都是B看A的抖动,通过优化(插值,平滑等),进一步减少这些后,B的感受是很好的。我们测试在200~300毫秒随机延迟的情况下,B的操作手感良好。
  这里,客户端提前服务器的方式,并且在延迟增大的情况下,客户端将加速

21.检测udp丢包,冗余,udp序号,重发,mtu限制udp最大一次发多少n
抗udp丢包:使用冗余包,具体冗余包几个,需要检测网络状况来进行动态调整,比较省流量
抗网络抖动:使用后模拟,不建议使用帧缓存,帧缓存会加大操作延迟
逻辑是66毫秒一次,1秒同步15个包
出现丢包时,改为可靠udp下发
检测丢包,丢包时重新请求

22.帧率和同步基础
渲染帧(一般为30到60帧),则是根据逻辑帧(10到20帧)去插值
相同的初始状态(不受画质,本地顺序和状态影响)
完全一致的输入驱动内容及顺序
​完全一直的代码执行流程(模块调用顺序,容器顺序)
完全一致的随机数生成规则,一致的调用时机及次数
尽量用整数实现各种游戏系统和基础数学库(浮点数运算有精度问题)
避免本地逻辑(比如获取本地的时钟参数之类的,即要保证使用的参数都是所有客户端都一致的)


23.客户端和服务端执行先后
#客户端先行,之后校验回扯,影子追随算法
若出现服务端发到客户端的数据都用完了,则表现层可按上一个操作的移动方向移动,接到下一个包时再纠正逻辑层,参考:航位预测算法
#预表现(客户端缓存接收到的操作,整体迟1,2帧执行,貌似王者就是这样的,不确定是否对)

24.平滑位移
1.分析延迟是否有逻辑问题,比如我们一开始有部分延迟是因为逻辑在FixedUpdate里执行,因为更新顺序问题导致客户端相应操作慢一帧,
修改Unity源码解决之后帧率有较大提升。2.网络层面极致优化,使用UDP并且使用非可靠UDP,通过冗余包的方式降低延迟冗余包的个数依赖MTU并不固定,
当然前提是每个帧操作的包也要极致优化。此外客户上行可以允许丢包,客户端操作的发送立即执行频率比逻辑帧高,使用多线程。3.最后还可以进行一些预表现。
比较好的做法逻辑层和变现层是分离的,客户端表现层可以预表现一定的位移,然后通过航位预测算法逐渐差值到逻辑层的位置,
这样就不会出现突然回头再网正确方向总的情况了。对于帧同步来说解决平滑位移的基础还是网络层的极致优化,然后再考虑平滑差值和预表现。
2.差值和预测

25.镜头移动
抬起,固定,下落

26.帧同步多单位寻路(包含动态遮蔽)
A*Pathfinding,需对部分算法float改为定点数

27.寻路
https://gameinstitute.qq.com/community/tag/%E5%AF%BB%E8%B7%AF%E7%AE%97%E6%B3%95
gadn个寻路
https://gameinstitute.qq.com/community/detail/119033
寻路演化


28.udp分类和选择
#纯udp
客户端向服务端
不等待服务端ack:网络好单发,网络差双发重复发送UDP包的机制,wifi下双发
等待服务端ack:同时冗余前2帧操作一起发送(只冗余前几帧中未收到ack的)
服务端向客户端
冗余发送,接到ack的帧不冗余
#可靠的udp
参考photon的网络
#混用tcp和udp
#raknet
#KCP协议

29.优化
1.对模型做LOD
2.减少GC的触发(很多时候大的卡顿点都是这个引起的)
3.不要在update做一些高消耗的调用,如GetComponet,Find,Load,SetActive,直接取transform等等
4对挂有Collider对象,修改位置或旋转不要直接改transform,把Rigidbody也挂上,通过修改Rigidbody的位置和旋转来达到移动的目的。
5.如果同时修改位置和旋转时,使用SetPositionAndRotation
6.离开视野的动画和粒子不要播放。
7.物理引擎消耗要注意。mesh顶点数不要太多。能用射线的就不要投射球体和立方体。
8.减低fixedupdate频率的限制来减少物理引擎的消耗。
9.遮挡剔除。
10.对不同机型做分级优化。
11.模型的面数要控制在一定的数量内。远处使用低模。太远的直接不显示。
12.模型骨骼数量不要太多。
13.drawcall数要控制
14.不要使用太过复杂的shader。物体能渲染一面的就只渲染一面。
15.限帧。
优化线程,寻找热点,看看是哪个函数消耗较高,把主要消耗的线程任务分配到空闲的线程,提高整体效率。优化物理,减少场景内没有必要的物理碰撞(和美术配合)
减少模型面数(和美术配合,做模型的LOD)优化材质(和美术配合),注视距裁剪,检测有没有空转的函数

30.请问AI在弯道漂移的时候怎么实现超车??
这跟弯道漂移的最佳时机和角度、当时车的速度、赛道宽度,不同车的动力等因素都有关,也跟车的动力模型算法有关,跟AI的实现方案也有关。也跟碰撞机制有关。所以很难简单说如何在弯道漂移时如何超车。
这里就从漂移过弯用时最小这个维度来考虑。如果AI的实现,采用的是遗传算法的话,那就是要多代训练AI车,加突变的因素。最后训练出最佳的AI数据

31.抗网络抖动
首先回滚机制是必须的
1.网络接收端做一个缓冲器(信号处理里面叫jitterbuffer),将接收到的包,适当均匀化,能起到一定的抗抖动作用,但是作用有限。
2.表现层继续保持自身的运动惯性,按照自己的预测(惯性)继续往前执行。
来位移来说,这个时候逻辑层位置和表现层位置就不一致了,等后面逻辑往前推进,得出新的位置,这个时候就将表现位置修正成对应的逻辑位置。
如何修正呢?
1)暴力拉扯,如你看到的,很多游戏的瞬移。
2)温柔修正,利用多余的位移,路径平滑等等方式修正,让用户看起来是流畅的。
3.怎么把握这个呢?
一般是表现层的预测有个阈值,可能是时间,或者其他的事件,对于位移可能是位移的距离,超过这个阈值可能停止预测。
修正上也有个阈值,低于某个阈值,温柔修正,超过某个阈值,暴力修正。

32.丢帧/包处理
由于TCP的丢包重传机制会导致较大的延迟,大多数情况下,帧同步都采用UDP协议进行网络通信,这就意味着需要自行解决丢包问题。
预防:关键帧数据包里携带前面两帧的数据,可大大降低丢包率,但会带来冗余的增加。因此,值得注意的是不能使用UDP数据包过大,否则部分路由器会在组合UPD分组时发生错误,建议不超过Internet标准MTU尺寸576byte。
补救:虽然上述方案能起到预防丢帧的作用,仍然无法避免丢帧问题。在出现丢帧问题时,需要客户端根据所需帧序号主动向服务器请求关键帧,包括单帧请求和批量帧请求。为了保证能够获取到所需关键帧,建议采用TCP协议

没有起手势的放技能需要评估是否能预表现,类似重要的技能不能预表现

34.基于资源id的对象池
自动化生成资源加载路径和资源id的映射
添加一个运行时资源id和实例对象池
获取时通过资源加载路径取得资源id,再通过资源id去获取实例对象池

35.碰撞检测
子弹用swept volume连续检测
书:实时碰撞检测算法技术

36.MMO游戏技能带击飞与位移时造成位置不一致
服务端作为仲裁
攻击判定模型的优化,客户端和服务器的位置或者方位,可能会存在一些粒度上的差异
所以,一般服务器的判定范围会更大一些,表现上用特效来缓解距离敏感度

37.帧同步动画表现
帧同步的做法中,逻辑层和表现层应该严格的分开,攻击、移动之类的属于逻辑层,而动画播放属于表现层,所以逻辑层的东西理论上是不应该对表现层有依赖,你这种表现层播动画,逻辑层根据时间(或帧数)来驱动的做法其实正是比较规范的做法
关于逻辑层如何得到时间,可以在开发工具流中解决,例如编写工具将动画时间统一导入到逻辑层用的表格中
在解决这个问题后,仍然留下的精确性的问题可以分两种,一种是时间上的精确,另一种是位置上的精确
时间上的精确性问题主要体现在两种表现衔接的时候,例如一个动画播放完成时需要立刻切入新的一个动画,如果中间有一帧间隔,则会发生表现上抖动,解决这个问题可以让美术将前一个动画稍作延长,多在最后一帧的姿态保留几帧,或是在适当的条件下直接将两段表现整体制作,整体播放,仅在逻辑层对前后两个时间段分割
位置上的精确性需求通常体现在需要从动画中获取具体骨骼或者碰撞体的位置,依赖这个位置做后续逻辑判断的情况。解决的办法仍然是将需要依赖的每帧的对象位置从表现层的动画文件中导入到逻辑层。这样逻辑层就能独立获取出相关的位置
如果要做得更完美,可以在表现层对输入进行预表现,在预表现结果和逻辑层实际运行结果有差异或差异过大时进行表现修正

38.navmesh的路点贴着边
用FindClosestEdge判断是否碰到了边,是则往法线方向矫正,需要自己控制位移

float radius = 1;
Vector3[] pathCorners;
if (NavMesh.CalculatePath(character.transform.position, targetPosition, NavMesh.AllAreas, navPath) && navPath.corners.Length > 1) {
                pathCorners = navPath.corners;
                for (int i = 1; i < pathCorners.Length - 2; i++) {
                    NavMeshHit hit;
                    bool result = NavMesh.FindClosestEdge(pathCorners[i], out hit, NavMesh.AllAreas);
                    if (result && hit.distance < radius)
                        pathCorners[i] = hit.position + hit.normal * radius;
                }
 

39.
http://www.zhust.com/index.php/2014/02/%e7%bd%91%e7%bb%9c%e6%b8%b8%e6%88%8f%e7%9a%84%e7%a7%bb%e5%8a%a8%e5%90%8c%e6%ad%a5%ef%bc%88%e4%b8%89%ef%bc%89%e5%b9%b3%e6%bb%91%e7%ae%97%e6%b3%95/
https://blog.csdn.net/weixin_33937778/article/details/86095157
立方体抖动平滑处理(Cubic Splines),这个东东比较复杂,他需要四个坐标信息作为他的参数来进行运算,第一个参数Pos1是OriginB,第二个参数Pos2是
OriginB在模拟运行一秒以后的位置,第三个参数Pos3是到达_TargetB前一秒的位置,第四个参数pos4是_TargetB的位置。

Struct pos {
Coordinate X;
Coordinate
Y;
}

Pos1 =OriginB
Pos2 = OriginB + V
Pos3 = _TargetB – V
Pos4 =_TargetB
运动轨迹中(x, y)的坐标。
x = At^3 + Bt^2 +Ct + D
y = Et^3 + Ft^2 + Gt +H
(其中时间t的取值范围为0-1,在Pos1的时候为0,在Pos4的时候为1)

x(0-3)代表Pos1-Pos4中x的值,y(0-3)代表Pos1-Pos4中y的值
A = x3 – 3 * x2 +3 * x1 – x0
B = 3 * x2 – 6 * x1 + 3 *x0
C = 3 * x1 – 3 * x0
D = x0

E = y3 – 3 * y2 +3 * y1 – y0
F = 3* y2 – 6 * y1 + 3 * y0
G = 3 * y1 – 3 * y0
H =y0

上面是公式,那么下面我们来看看怎么获得Pos1-Pos4:首先,Pos1和
Pos2的取值会比较容易获得,根据OriginB配合当前的速度和方向能获得,然而Pos3和Pos4呢,怎么获得呢?如果在从Pos1到Pos4的过
程中有新的PDU到达,那么我们定义他为NewPackage。

Pos3 = NewPackage.X+ NewPackage.Y * t + 1/2 * NewPackage.a * t^2
Pos4 = Pos3 – (NewPackage.V +NewPackage.a *t)

40.lua和c#混用怎么高效共享数据
直接将数据存储到Lua分配的数组中,然后将Lua底层的内存结构直接暴露给c#,c#绕过lua api,直接以unsafe的方式直接读写Lua的内存
这样,Lua可以以原生的效率读写Lua数组,而C#以 unsafe的方式读写Lua内存,也可以做到近似原生访问数组的效率。

41
放在Lua的功能(网络同步/数据表/玩法视则/声明問期管理/技能A模板节点)

43.pbr
材质在真实物理模式下的反应
间接光+直接光+AO+GI(全局光)+Light+Sub+pbs = pbr
漫反射( Diffusion)&反射( Reflection
金属(导电材料)&非金属(绝缘体
能量守恒 材质不会比入射光更亮
菲涅尔( Fresnel)
金属本质上反射占了绝大部分
优势:避免shader爆炸,何不dc,项目复用
lwrp 和 hdrp

44
角色头部相关技术
1. 丰富的面部表情,通过morph网格变形实现,需要做一个编辑器,供美术调整面部顶点,并记录下来,供后期VS混合时使用。
2. 活动的头部,通过控制颈部的骨骼,限制一定的活动范围,达到“注视”目标的效果。
3. 活动的眼球, 眨动的眼皮。WOW的实现是,眼眶是固定的模型,眼睛是一个片,可以左中右三方向随机固定运动,至于眨眼,只是随机时间内短暂出现一个皮肤色的片,暂时挡住眼球部位,实现瞬间的眨眼效果。这样的细节程度,可以满足WOW这种近境渲染不多的游戏;对于生化五这种注重人物表现的游戏,通常的实现应该是,眼眶的顶点可活动,随时时间播放“合拢”动画,达到眨眼效果,也可以实现各种“瞪眼”和“眯眼”的效果。而眼球,一个半圆的球面,通过一根骨骼控制,同头部实现。

光照相关技术
1. self shadow,众多shadow map,我选择的是vsm,它在实现self shadow的效果中表现良好,一定程度模拟出soft shadow的效果,部分精度、走样和漏光都可以解决,通过淡化阴影,效果不错。 
2. 3S(subsurface scattering),次表面散射是指光线照射到物体后,进入物体内部,经过在物体内的散射从物体表面的其他顶点/像素离开物体的现象。在透明/半透明的材质上,这种效果比较明显,比如说是皮肤,玉等等。怎么理解?请把手张开,挡住太阳,然后观察这只手。可以说这种效果是比较难以模拟的,在没有复杂的预计算前提下,很难达到接近物理真实的。这里通过在ps中拿到已经projection后的z,即可以简单的理解为散射的一个参数,通过一些非物理的计算,控制住其散射的比例,以达到柔和的效果。
3. rim lighting,皮肤边缘上的汗毛和毛孔能捕捉光线,当光源正对着摄像机时,你可以观察到这种效果。这里可以通过1 - VdotN来达到这个目的,为了得到更好的边缘,最好将oneMinusVdotN再乘以自己一次。是的,因为是VdotN,这种边缘模拟实现只在高镶嵌的模型上效果最好,对于低模,相对效果要差一点。

45. FPS和 赛车类游戏同步,每秒发包量(10-30个)

46.问题
创建销毁的时机,子弹创建后回滚怎么处理
判断哪些能预测,哪些不能。位移可以,飘血和掉血不能,无前摇动作的大招不能
回滚时如果原本已有的对象,又触发了创建,怎么检测是否创建
回滚时如果需要销毁,在什么时机进行回收
回滚时如果需要创建不存在的对象,那么创建后是否会引起不同步

寻路完成再发起技能消息
单次寻路和分帧寻路

48.寻路和RVO结合
小兵重叠多,使用分层寻路,英雄寻路格子小,小兵寻路格子大,小兵间隔就大。英雄和小兵间隔小,利于补兵
高层寻路用a星,比如 dijkstra
底层碰撞避障用VO, RVO, ORCA
KDTREE
这个是一个简单的KDTREE实现 用来寻找距离最近的障碍(指定数量) 优化RVO的资源消耗.
VO Velocity Obstacles 速度障碍
核心思想: 只要在未来有可能会发生碰撞的速度 都排除在外
抖动现象: 两个位移单位存在可能会发生路径碰撞的情况下会同时采取保守的避让速度,导致新速度偏离过大又大幅度回归,从而产生震荡.
RVO Reciprocal Velocity Obstacles 互惠的速度障碍
核心思想: 优化VO思想, 假定对方也会采取避障行为, 缩小(average VO)速度.
ORCA Optimal Reciprocal Collision Avoidance 最优互惠碰撞避免
核心思想: 优化RVO, 额外考虑速度大小, 求解过程使用线性规划,更高效简洁.
对其他所有agents的ORCA求交(线性规划),再与自己可选速度求交集,得候选速度集ORCAτA
在候选集中求解跟自己偏好速度最近的一个速度vnewA

49.
快速排序(分治思想的应用):不是任何情况都适用,数据量小的话,还不如冒泡快,但快排的确很优秀。
堆排序:可用于做游戏排行榜前多少多少名,根据求最大的K个数还是最小的K个数来建最大堆和最小堆,再将最大/小堆的根节点和最后一个子叶节点交换,最后调整堆,重复刚才那两个步骤,直到得到K个数。当然,这种题也可以用红黑树实现的set来做。
二分查找:用于查找出分数为多少多少的玩家

50.异步处理创建销毁
将创建对象记录到待创建队列中。开启一个协程,while处理这个对列

51.消除配置文件中冗余数据,达到压缩和优化数据存储的方法

52.模拟网络环境
network link conditioner
Network-Emulator-Toolkit
https://blog.csdn.net/no1mwb/article/details/53638681

53。kcp客户端和服务端收发顺序
服务端
接收:udp->kcp->分发
发送:调用发送-》kcp-》udp-》client
客户端
接收:ucp->kcp->分发
发送:调用发送-》kcp-》udp-》server

54.udp重发
如果包1没收到,包2收到了,那么初步判断包1丢失了,当然也可能是乱序,我们会等待100ms再判断是否要求Server重传。
网络上肯定存在很多种方案,我们也测试过很多方案,包括超时重传,丢包立即重传,最终方案是这两个的结合体,只不过超时是固定时间100ms,取得结合流量和延迟的平衡。
动态冗余算法,通过每个客户端的丢包状况来动态调整冗余倍数
mtu设置的太大会导致:数据报太大,被分组,部分路由器组包错误(极小概率)。使用576byte的mtu解决

55.udp回发的问题
服务器每次收到包就都记下回发地址,这样中间路由如何变化,都可以送回去,否则可能因中间路由变化了,导致客户端收不到包

不使用tcp:丢包较多,TCP可能会频繁断线

56.网络数据压缩
压榨Input尺寸,Input –> 16byte。

降频:
relay server试图从30帧/s降低到15帧/s,渲染仍然30帧以上,但是操作手感有些下降,其实未采用。

Input去重复
玩家实际操作中会有,按键并非每帧都变,所以可以不上报。

帧间压缩
帧间压缩的概念是,玩家的两次按键变化之间,只是部分数据变化。比如: 按住left+shift为向左加速跑,松开shift,变为向左走步。 该变化只是shift按键,数据是1个bit。
具体做法是当前帧和上一帧异或,这样就出现很多0值,很容易压缩掉。
客户端还原时,只需要保存上一帧的数据, 则总是能正确还原出当前帧。

Frame压缩
在上述基础上,再进行RLE压缩,进一步压榨。使用RLE算法原因是速度和效果的平衡。

57.抖动:
Jitterbuffer算法
MAX算法原理
取最高点之上作为Jitterbuffer的上限。估计未来只有极小可能超过大于该值,再通过一些算法,消除大个毛刺

58.航位预测
#位置1 = 位置0 + 速度 × (T1 – T0),相当于根据PDU中的数据,做匀速运动
#位置1 = 位置0 + 速度 × (T1 – T0)+ 1/2 × 加速度 × (T1 –T0)平方,比2)中多了加速度的方向

59.延迟补偿
通常用于fps,mmo
开枪这种指令传开枪时间到服务器上,服务器计算服务器接收时间-2*RTT,用这个时间把敌人回退到开枪的这个瞬间,然后计算是否命中
不可避免的问题:
看见敌人的瞬间就躲到掩体里,但是敌人还是爆了你的头-这是有可能的,因为本地的反应操作还没有同步到服务器上,而服务器上计算命中是一个延迟补偿的,所以在服务端的位置还在原地,计算结果还是被击中

60.mmorpg位置同步
mmorpg的位置同步大致要考虑以下问题
1. 精确性
2. 容错性
3. 性能
4. 防作弊
基本原则:位置以服务器为准,大家同步某时谁出现在什么位置,参与方基于相同时刻的位置来同步、回放和校验。出现问题则拉回到服务器位置。
大致思路:客户端寻路,同步移动状态,服务器校验。
具体来说,首先客户端和服务器应该有一个全局时间,基本上可以认为服务器时间就是全局时间。
客户端向服务器发送两种同步消息,A是{当前位置,当前全局时间,当前速度,目标路径},这种用来同步位置。另外一种B是{当前位置,当前全局时间}用来给服务器校验二者的位置。
当用户速度发生变化的时候,就给服务器发送A消息,然后服务器校验位置和时间的合法性,合法则广播给其它用户(可以考虑计算出到达目标的时间,便于同步)。这种做法会大大减少移动同步信息的数量。在移动过程中,用户定时给服务器发送B消息,服务器可以根据这个消息来判断当前位置的合法性。
TODO::为了更加真切的同步,客户端接收到寻路消息,不立刻回放,而是预留100ms再动,这100m用来给服务器和其它用户同步。或者头几ms变速移动,不是一下就达到最高速度,而是逐渐过渡到。

61.多版本兼容
要求:服务端保留表结构crc校验
做法:
在发布版本前预留多n张表,表里除了id其余字段用string类型表示,程序开发兼容的版本时,去手动解析字符串
缺陷:不能增减列数,不能加表,需要做多版本管理,只有在发下一个不兼容的版本时才能更新表字段名,需在配置里控制指定版本是否需要更新到最新版本

62.shader调试方法
*1.要知道一个值大概是多少,可以用if,大于指定值输出红色,小于输出绿色,等于输出黄色
*绘制数字在屏幕上
https://www.cnblogs.com/freeblues/p/5724774.html

63.预防由资源引起的各种消耗
可考虑在导入资源时对资源进行检测,生成资源数据报告,比如面数,顶点数,文件大小,命名规范,设置的限定(比如纹理的mipmap),特效的检测,ui透明部分比例检测,视频分辨率检测
数值可用UnityEditor.UnityStats获取

64.碰撞检测
不旋转的碰撞:AABB,比较碰撞体点的包含关系
旋转的碰撞:obb,大概原理是以边为轴,碰撞体的每条边都投影到这几个轴上,如果边都有重叠关系则判定为碰撞
GJK算法+四叉树空间分割

65.fps 同步方案
https://my.oschina.net/kkkkkkkkkkkkk/blog/3125586

66.ai,碰撞引起的navmesh寻路问题
数据层的角色有时会跑到nav不可达的地方,一旦到了这种地方,nav就无法寻路了,出现对着墙跑的情况
换成a*+堆排序

67.FPS中枪穿模问题
使用子摄像机单独渲染手臂,缺点是墙上枪的影子会有点问题
在贴近墙时,播放垂下枪的动画

68.判断目标和自身的方位
    1.判断目标在自己的前后方位可以使用下面的方法:
    Vector3.Dot(transform.forward, target.position)
    返回值为正时,目标在自己的前方,反之在自己的后方

    2.判断目标在机子的左右方位可以使用下面的方法:
    Vector3.Cross(transform.forward, target.position).y
    返回值为正时,目标在自己的右方,反之在自己的左方

    向量的点积和叉积:

    A.点积 
      点积的计算方式为:  a·b=|a|·|b|cos  其中|a|和|b|表示向量的模,表示两个向量的夹角。另外在 点积 中, 夹角是不分顺序的。 
      所以通过点积,我们其实是可以计算两个向量的夹角的。 
      另外通过点积的计算我们可以简单粗略的判断当前物体是否朝向另外一个物体: 只需要计算当前物体的transform.forward向量与 otherObj.transform.position 的点积即可, 大于0则在前方,否则在后方。

    B.叉积 
      叉积的定义: c =a x b  其中a,b,c均为向量。即两个向量的叉积得到的还是向量! 
      性质1: c⊥a,c⊥b,即向量c垂直与向量a,b所在的平面 。 
      性质2: 模长|c|=|a||b|sin 
      性质3: 满足右手法则 。从这点我们有axb ≠ bxa,而axb = – bxa。所以我们可以使用叉积的正负值来判断向量a,b的相对位置,即向量b是处于向量a的顺时针方向还是逆时针方向

69.随机
    柏林随机(Perlin)
    https://www.jianshu.com/p/27e29d50116b
        常用于生成噪声,随机地形,火焰燃烧        
    高斯分布随机(Gaussian Randomness)
        高斯分布大部分的点都在中心,中心极限定理
        普通的伪随机分布的比较均匀,高斯分布更加真实
        例如:fps游戏用高斯分布随机瞄准能射的更接近中心准,爆头率更高。
        double GetGaussianRandom(int seed = 61829450)
        {
            double sum = 0;
            for (int i = 0; i < 3; i++)
            {
                long holdseed = seed;
                seed ^= seed << 13;
                seed ^= seed >> 17;
                seed ^= seed << 5;
                r = holdseed + seed;
                sum += (double)r * (1.0 / 0x7FFFFFFFFFFFFFFF);
            }
            return sum;
        }
    过滤随机
        对产生的随机数进行过滤
        例如:随机暴击率中希望连续出现暴击可以用这个
        过滤整数
            1.    有两个重复的随机数,则重新随机新数
            2.    隔了一个数的重复,如“8, 3, 8” 或 “6, 2, 6”
            3.    太多极限值出现,即太多大值和小值,如“6, 8, 7, 9, 8, 6, 9”
            4.    10个数以内出现两组重复数如“5, 7, 3, 1, 5, 7”
            5.    连续上升,或连续下降,如“3, 4, 5, 6”
            6.    10个数之内重复次数过多,如“9, 4, 5, 9, 7, 8, 9, 0, 2, 9”
        过滤浮点小数
            0-1之间的浮点数
            1.    两个连续数字相差小于0.02,如0.875 和0.856
            2.    三个连续数字相差小于0.1,如0.345, 0.421, 和0.387.
            3.    五个连续增加或连续下降,如0.342, 0.572, 0.619, 0.783, 和0.868.
            4.    太多极限值出现,即太多大值和小值,0.325, 0.198, 0.056, 0.432, and 0.216.
如果想要产生的结果更真实,就是用高斯分布随机。如果为了让玩家更开心,可以对产生的随机数进行过滤


70.游戏时区问题
https://www.cnblogs.com/leoin2012/p/10970497.html
方法:根据服务器时区进行统一显示
游戏内的时间显示/计算都以服务器时区为准,方便之后国际化
而各种语言关于时间函数的api,需注意是否是按本地时区计算返回结果的
以Lua为例,Lua标准库中提供的时间函数 os.time()和os.date(),这两个函数传入和返回的时间table就是以本地时区为准的。

以下lua案例为开服时间的判断
通过TimeUtils.Date()、TimeUtils.Time()替代os.date()、os.time(),业务逻辑处理时间计算时,
只需考虑服务器时区即可,即使日后游戏进行国际化,只需根据地区修改ServerTimeZone即可,对业务层没有影响
    -- 服务器下发的时区,这个值是服务器下发的,如果是东八区则如下
    local ServerTimeZone = 3600 * 8

    -- 替代os.date函数,忽略本地时区设置,按服务器时区格式化时间
    -- @param format: 同os.date第一个参数
    -- @param timestamp:服务器时间戳
    function TimeUtils.Date(format, timestamp)
        local timeZoneDiff = ServerTimeZone - TimeUtils.GetLocalTimeZone()
        return os.date(format, timestamp + timeZoneDiff)
    end

    -- 替代os.time函数,忽略本地时区设置,返回服务器时区时间戳
    -- @param timedata: 服务器时区timedate
    function TimeUtils.Time( timedate )
        local timeZoneDiff = ServerTimeZone - TimeUtils.GetLocalTimeZone()
        return os.time(timedate) - timeZoneDiff
    end

    -- 获取客户端本地时区
    --lua里没有直接获取本地时区的api,但通过os.date("!*t", os.time()),可以获取格林尼治的时间table
    --再以本地时区解析table获取时间戳,该时间戳与os.time()时间戳相减即为时区秒数差值
    function TimeUtils.GetLocalTimeZone()
        local now = os.time()
        local localTimeZone = os.difftime(now, os.time(os.date("!*t", now)))
        local isdst = os.date("*t", now).isdst
         --isdst用于判断玩家本地设置时区是否正在实行夏令时,如果是则加3600秒
        if isdst then localTimeZone = localTimeZone + 3600 end
          return localTimeZone
    end

    -- 获取开服第二天0点时间戳
    local nextDayTable = TimeUtils.Date("*t", openServerTime + 86400)
    local nextDayZeroHourTime = TimeUtils.Time({year=nextDayTable.year, month=nextDayTable.month, day=nextDayTable.day, hour=0,min=0,sec=0})

71.跑酷类游戏的地图障碍生成
  这是跑酷类的核心技术点。一个跑酷游戏可能有多种不同地形:平地,悬崖,立柱等等,完全随机地去生成跑酷路径是有问题的:
  1.可能会出现不合理的路径:例如连续生成了10格悬崖地形,这么大的一个空隙玩家是不可能跳过去的;
  2.路径生成不能过于杂乱无章:跑酷游戏是技巧性游戏,玩家通过训练提高自己技巧获取更高分数,从而产生快感,路径生成的过于杂乱无章,就无技巧可言,没有可玩性;
  3.完全随机生成地形,就无法把控难度

  结论是:要做到既保证路径随机性,又保证路径可调控。
  解决方案是,不做成每个格子都随机生成,而是地图样式随机生成,地图样式是指一段预设计好的路径样式;比如地图样式1是连续3格距离悬崖,4格之后有一格立柱,其余为平地;
    地图样式2则是有两段悬崖...随机生成一连串的地图样式,一局游戏的路径就生成了。在此为基础,还可以再为不同样式的生成概率做不同权重,样式序列预设

72.shader打包到AB包后,没有包含到UnityShaderVariant导致模型变黑的问题
ShaderVariant
    在shader最终编译时,根据上层的宏定义,根据不同的组合编译出多套底层shader.
    Unity的Shader中通过multi_compile和shader_feature来定义宏(keyword)。最终编译的时候也是根据这些宏来编译成多种组合形式的Shader源码。其中每一种组合就是这个Uniy Shader的一个Variant
Material与ShaderVariant的关系
    一个Material同一时刻只能对应它所使用的Shader的一个variant。进行切换的要使用Material.EnableKeyword()和Material.DisableKeyword()来开关对应的宏,
    Unity会根据你设定的组合来匹配响应的shader variant进行渲染
    Shader.EnableKeyword,和Shader.DisableKeyword是对Shader进行全局宏设置
    built-in Shader的源码中的StandardShaderGUI.cs可以看到unity是怎么处理对于StandardShader的keyword设置的
multi_compile和shader_feature的区别
    如果你在shader中添加了
    #pragma multi_compile  _A _B
    #pragma multi_compile  _C _D
  那么无论这些宏是否真的被用到,你的shader都会被Unity编译成四个variant,分别包含了_A _C,_A _D, _B _C,_B _D四种keyword组合的代码
    如果是
    #pragma shader_feature _A _B
    #pragma shader_feature _C _D
  那么你的shader只会保留生成被用到的keyword组合的variant
判定feature是否被用到的机制
    1.如果shader没有与使用它的材质打在一个AB中,那么shader_feature的所有宏相关的代码都不会被包含进AB包中(有一种例外,就是当shader_feature _A这种形式的时候是可以的),这个shader最终被程序从AB包中拿出来使用也会是错误的(粉红色).
  2.把shader和使用它的材质放到一个AB包中,但是材质中没有保存任何的keyword信息(你再编辑器中也是这种情况),shader_feature会默认的把第一个keyword也就是上面的_A和_C(即每个shader_feature的第一个)作为你的选择。而不会把_A _D,_B _C,_B _D这三种组合的代码编译到AB包中。
  3.把shader和使用它的材质放到一个AB包中,并且材质保存了keyword信息(_A _C)为例,那么这个AB包就只包含_A _C的shaderVariant.
解决方案是 ShaderVariantCollection
    ShaderVariantCollection(简称SVC),这个SVC文件可以指定某个shader要编译都要编译带有哪些keyword的变种。并且在ProjectSetting->Graphics界面新加了一个Preloaded Shaders列表,
    可以把SVC文件放进去,编译时指定的Shader就会按照SVC中的设置进行正确的variant生成,而不会像Always Include Shaders列表中的那样全部变种都生成
另外2种方法,但会编译所有变体,导致包体变大
1.把Shader放到在ProjectSetting->Graphics->Always Include Shaders列表里,Unity就会编译所有的组合变种。
2.把Shader放到Resources文件夹下,也会正确处理,也应该是全部keyword组合都编译


73.战争迷雾(适合自顶向下看的迷雾)
总体来说就是在c#侧检测格子对应的迷雾状态,将迷雾的开启记录到Texture中,然后将Texture用shader绘制到mesh上
每个一定时间检查一次地图上格子的可视状态
将地图划分为n*n个格子,视野点p周围半径r范围内为可视区域,遍历(p.x-r,p.y-r)到(p.x+r,p.y+r)的格子,取距离的平方小于r*r的格子存入列表l
用Physics.CheckBox找出地图上的障碍格子
遍历l找到l中的所有障碍物
有 Color32[] colorBuffer结构存储每个格子的颜色和alpha值
检查这些障碍物是否有遮挡住l中的格子,如果有遮挡则被遮挡的格子对应的颜色为的r设为0,
没有被遮挡的r设为255,同时将r为255的格子设置到b中(b是迷雾已开启的所有格子)
之后根据colorBuffer的r和b找出 已开启但不在视野内的 格子将a设为120,不在视野内的设置为255,也就是不透明
之后将colorBuffer设置到texture中,对texture进行模糊处理
之后将这一帧的迷雾texture作为当前的迷雾结果,同时记录到lastTex中,用于下一帧的迷雾变化的过渡(插值)

74.Buff的结构
Caster,Ability以及Context
Caster代表Buff的施加者
Buff合并问题,两个buffid相同且Caster相同,则buff层数叠加
同时有个参数noCheckCaster控制不进行Caster检测实现 多人给boss叠buff的效果 或者环境buff的效果,持续的状态buff的效果(例如吃了经验卡双倍经验buff,活动buff,雪地冻伤buff)
设置buffTag时通过1 << 2和buffTag = Magic|Wood(代表木系魔法)

Buff相关对象和参数
Caster
    施法者
Ability
    buff是哪个技能创建的,允许为空
BuffTag
    代表这个buff是什么类型的,允许复合类型buff
ImmuneTag
    免疫哪个类型的buff
Context
    Buff创建时的一些上下文数据,是一个不确定的项,通过外部传入自定义的数据,然后在Buff逻辑中使用这些自定义数据
MotionPriority
    运动优先级,每个运动都有优先级,例如A在C动作事件中(冲刺)时,被B的D动作(轻踢)击中了,C优先级高于D则,打断无效,B会被A冲飞。
    如果时被B的E动作(重踢)击中了,且E优先级高于C,则A的冲刺被打断且会被踢飞
ForceInterrrupt
    强制打断动作,忽略优先级

Buff执行流程
    Buff创建前检查当前Buff是否可创建。一般主要是检测目标身上是否存在免疫该Buff的相关Buff,如果被免疫则不会创建该Buff。
    Buff在实例化之后,生效之前(还未加入到Buff容器中)时会抛出一个OnBuffAwake事件。如果存在某种Buff的效果是:受到负面效果时,驱散当前所有负面效果,并给自己加一个护盾。那么这个时候就需要监听BuffAwake事件了,此时会给自己加护盾,并且把所有负面Buff驱散。这意味着一个Buff可能还未生效之前即销毁了(小心Buff的生命周期)。
    当Buff生效时(加入到Buff容器后),我们提供给策划一个抽象接口OnBuffStart,由策划配置具体效果。
    当Buff添加时存在相同类型且Caster相等的时候,Buff执行刷新流程(更新Buff层数,等级,持续时间等数据)。我们提供给策划一个抽象接口OnBuffRefresh,由策划配置具体效果。
    当Buff销毁前(还未从Buff容器中移除),我们提供给策划一个抽象接口OnBuffRemove,由策划配置具体效果。
    当Buff销毁后(已从Buff容器中移除),我们提供给策划一个抽象接口OnBuffDestroy,由策划配置具体效果。
    Buff还可以创建定时器,以触发间隔持续效果。通过策划配置时调用StartIntervalThink操作,提供OnIntervalThink抽象接口供策划配置具体效果。
    Buff还可以通过请求改变运动来触发相关效果。通过策划配置时调用ApplyMotion操作,提供OnMotionUpdate和OnMotionInterrupt接口供策划配置具体效果。

Buff修改玩家的状态(ModifyState)
Stun(眩晕状态——目标不再响应任何操控)
Root(缠绕,又称定身——目标不响应移动请求,但是可以执行某些操作,如施放某些技能)
Silence (沉默——目标禁止施放技能)
Invincible (无敌——几乎不受到所有的伤害和效果影响)
Invisible (隐身——不可被其他人看见)

Buff修改属性(ModifyAttribute)
    分为原型数据和非原型数据,两者加起来

Buff修改运动(ModifyMotion)
    运动轨迹:直线/斜线(突刺,翻滚,子弹运动,击退),曲线(击飞,飞起降落,轻功)

Buff监听事件
    OnAbilityExecuted,监听某个主动技能执行成功。常用于被动技能Buff,比如说角色施法时有10%概率获得30%的攻速提升。那么我们通常是Buff-A监听OnAbilityExcuted事件,然后10%概率添加Buff-B。Buff-B的作用是修改玩家属性,增加30%攻速。
    OnBeforeGiveDamage,OnAfterGiveDamage监听我方给目标造成伤害时触发。比如说对目标造成的伤害有10%概率无法被闪避,那么这个效果我们就可以通过监听OnBeforeGiveDamage的流程来实现。当执行伤害流程时,在计算伤害前我们抛出一个事件event。event里面有当前伤害数据。Buff在调用OnBeforeGiveDamage(event)时,修改event.Damage.DamageFlag |= DamageFlag_NotMiss,标注该伤害无法被闪避就行了。又或者如果有一个需求是给目标造成伤害后有10%几率触发DOT伤害效果,那么我们在OnAfterGiveDamage的时候取出event.Target并给这个目标加个DOT类Buff即可。
    OnBeforeTakeDamage,OnAfterTakeDamage监听我方受到伤害时触发。如护盾类Buff通常在OnBeforeTakeDamage的时候修改伤害数据。又或者有某些Buff在受到伤害后可以触发各类效果就可以通过监听OnAfterTakeDamage事件来触发指定逻辑。
    OnBeforeDead,OnAfterDead监听我方死亡时触发。如免疫致死效果可以通过监听OnBeforeDead事件修改角色当前的Hp>0,从而让角色提前退出死亡流程以避免死亡。死亡后触发额外效果,如爆炸或者召唤其他生物都可以通过监听OnAfterDead事件来执行。
    OnKill事件,监听我方击杀目标时触发。如当击杀目标后获得治疗效果回复即可通过监听到Kill事件时给自己加一个HOT的Buff来实现。

子弹的碰撞盒时等腰梯形
dot伤害:每回合持续伤害
--------
优化方案:
1.lua表优化
采用默认的table来取代最高频率出现的表,可优化配置表的大小和占用内存

2.减少中间语言的生成
// 指示编译器,除非定义了指定的有条件编译符号,否则,应忽略方法调用或属性      
// 该特性对类和方法有效,同时仅当类型派生自Attribute时,对类使用此特性才有效
[Conditional("DEBUG")]


--------
调试&分析:


-------- 
 插件
 WorldStreamer  - 无限大地图
 Apex Path - 寻路,据说代码结构很好

---------------
slg技术选型
地形编辑、行军寻路(需要支持关隘和高地)
100多个建筑+几十个NPC+场景本身和UI部分的所有性能消耗
同屏500+的单位同时战斗,,每个AI都需要有自己的AI机制和独立的动作表现。一个单位大概会有5-6中动作,小型单位600+面,大型单位1000+面
战斗还需要支持录像回放,战斗支持倍速功能
服务器:
    skynet https://link.zhihu.com/?target=https%3A//github.com/cloudwu/skynet
    基于Actor模型

ecs
    e是实体
    c里只有数据,没有方法
    S的概念就是为了解决耦合和数据冗余,所有方法写在s里


协议传输:sproto https://github.com/cloudwu/sproto
针对sproto的C#的转译工具,可以将Sproto的描述文件转为CS文件,一套序列化和反序列化工具
策划配置表:excel转Sproto的工具(先把Excel转成Lua格式,再Lua转成Sproto的描述文件,再把描述文件转为CS)

gpuskin+gpuinstance
    小米5S做过一次测试,当使用skinmesh的时候,4000单位的帧率就只能到20了,换了GPUSkin方案,8000个单位仍然能够保持50帧

xlua

其余
    防御性编程,做好各项QA验收
    对每个功能做出屏蔽入口,甚至在一些运营活动上做好模板参数,可以通过快速调节参数就能变成另外一个活动

音效框架
    wwise和fmode(fmode没有wwise好)

gcloud
addressable asset system
timeline
tiled
a*PathFinding
 

你可能感兴趣的:(笔记)