- 定制进度条
- 根据坐标求角度
- Stack-Based FSM
- 大厅技能、武器界面(2020.11.17)
- 处决UI(2020.11.25)
- 连击计数(2020.12.7)
- 结算(2020.12.16)
- 场景中人物拿枪(2020.12.30)
- 大厅人物模型动画性别区分(2021.1.14)
- 手雷伤害计分(2021.1.15)
- 交互物品触发器框架
- delegate通知实现
- Bug合集
定制进度条
ue4居然只内置了普通的bar状进度条,如果需要其他形状的需要自己实现。
首先准备一张黑白的圆形渐变图(circular gradient),它从中心点到边缘的连线的灰度值一致,且按顺时针(或逆时针)从黑色(0)线性变化到白色(1)。接着在蓝图中实现一个根据输入percent来筛选alpha值的逻辑,小于输入为1,大于等于为0,就得到了一张扇形比特图遮罩,再用这张遮罩去multiply一张环形texture,就得到了该percent下的圆形进度条。
根据坐标求角度
贴一段引擎中定义的Atan2
函数代码
float FGenericPlatformMath::Atan2(float Y, float X)
{
//return atan2f(Y,X);
// atan2f occasionally returns NaN with perfectly valid input (possibly due to a compiler or library bug).
// We are replacing it with a minimax approximation with a max relative error of 7.15255737e-007 compared to the C library function.
// On PC this has been measured to be 2x faster than the std C version.
const float absX = FMath::Abs(X);
const float absY = FMath::Abs(Y);
const bool yAbsBigger = (absY > absX);
float t0 = yAbsBigger ? absY : absX; // Max(absY, absX)
float t1 = yAbsBigger ? absX : absY; // Min(absX, absY)
if (t0 == 0.f)
return 0.f;
float t3 = t1 / t0;
float t4 = t3 * t3;
static const float c[7] = {
+7.2128853633444123e-03f,
-3.5059680836411644e-02f,
+8.1675882859940430e-02f,
-1.3374657325451267e-01f,
+1.9856563505717162e-01f,
-3.3324998579202170e-01f,
+1.0f
};
t0 = c[0];
t0 = t0 * t4 + c[1];
t0 = t0 * t4 + c[2];
t0 = t0 * t4 + c[3];
t0 = t0 * t4 + c[4];
t0 = t0 * t4 + c[5];
t0 = t0 * t4 + c[6];
t3 = t0 * t3;
t3 = yAbsBigger ? (0.5f * PI) - t3 : t3;
t3 = (X < 0.0f) ? PI - t3 : t3;
t3 = (Y < 0.0f) ? -t3 : t3;
return t3;
}
Stack-Based FSM
前面和背包有关的UI框架搭的差不多了,现在有一个优化的任务,首先现在ui资源用的是散图生成的texture,希望出包的时候换成整图texture,节省空间降低draw call,其次是需要一个UIManager统一管理ui,目前显示ui和控制的逻辑基本都放在PlayerController里面,未来如果界面多起来会很冗余。
查ue4的ui管理的时候看到一个用栈状态机来优化的文章,感觉很合理,就查了一下这方面的资料。
FSM:有限状态机(Finite-State Machine),又叫有限自动机(finite-state automaton),一般来说分两种,确定有限状态机(deterministic finite-state machine)和非确定有限状态机(non-deterministic),这里确定指的是每次在特定条件输入时状态转移的唯一性,即对于所有输入都有且仅有一条transition,任意NFSM可以由DFSM所构建,反之亦然。
PDA:下推自动机(pushdown automaton),指的是操作一个栈的FSM
大厅技能、武器界面(2020.11.17)
比较基础的tab页签实现:一般用一个WidgetSwitcher做切换,当页签按钮按下时触发切换,由于引擎自带button不能传参,可以使用之前封装的CustomizedButton来根据index区分每个按钮,减少代码冗余。
根据数据加格子,点击格子显示选框,点击界面按钮对格子道具进行操作:选中格子显示ClickFrame,并把之前选中的消掉,这要求界面储存格子ui的实例,然后点击时分别去设置上次点击的grid和这次点击grid的表现。
技能表中按效果id索引,同一个技能可以升级对应不同的效果,因此索引后可以查到技能id和技能等级,服务器储存的是技能id和等级,这样方便升级,如果存效果id每次升级时都要查一次表,找到下个等级对应的效果id,但如此一来技能每次更新刷新表现时都要根据这两个信息去找效果id,升级的次数是有限的,但切换技能的次数是无法估计的,所以我认为直接用效果id存比较好。
处决UI(2020.11.25)
杀死敌人之后充能,一定数量后激活处决技能,使用后清零:现在处决是按下Ctrl键就触发,充能数和充能阈值是两个属性,ui上监听充能数的变化,当它超过阈值就允许ui执行Ctrl。也就是说ui不用关心充能是如何累加的。
如何判断该NPC是谁杀死的? 当时纠结这个问题是因为想在npc死亡时做逻辑,但其实处决有自己的状态通知,开始、找到目标、结束等,应该用处决定义的通知去处理。
如何找到处决目标并下发事件? 还没有去看具体的实现,但我想的话无非就是处决条件满足时根据与npc的碰撞(或计算距离)来判断是否可以执行,如果是则发出找到目标的通知,
连击计数(2020.12.7)
打中npc计入属性,监听属性变化,有变化时调用回调函数,更新ui
在连击状态的时候角色身上有tag和buff,需要用到AbilitySystemComponent FGameplayEffectSpec来获取container tag储存在定义好的FGameplayTagContainer中
连击时需要有一个倒计时的进度条表现,击中npc会回满,未命中会减少,这个效果通过定义一个timer,倒计时的总时间是知道的,因此在固定的时间间隔下就能计算出每次间隔需要减少的百分比,按这个百分比更新就行了。后面增加了一个需求,要求最后几秒钟有一个保护时间,并且这个时间所占的进度条比例可以配置。要实现这个需求就需要计算两段每次更新百分比,还是在同一个timer下更新。
未命中反馈通过比较新通知的百分比和之前储存的百分比来判断,如果新同步的百分比比之前小,那就说明发生未命中,播放相应动画。
客户端不能自己修改人物属性,要在服务器改。
加分Handle(2021.1.6)
在伤害计算时可以知道玩家击杀了某个npc,然后把分数加到相应属性上,但是手雷击杀、处决等不走常规伤害逻辑,这些情况的击杀如何加分? 手雷、处决击杀都有触发时的GE,之前加分是直接在其生效的时候通过修改属性的方式加分,现在只要将Execution中增加新写的handle,然后把加分的modifier删掉。
结算(2020.12.16)
遇到GameOver触发器时触发结算,结算时需要显示自己和队友的数据:
类似于队友血条同步,只是没那么频繁。结算请求由触发器触发,可能是通关、死亡、主动退出等,触发结算时要先从GameOverInfo中拿要退出玩家的controller,如果在其中找到了自己的controller,则请求结算,结算时根据mapid读表,计算获得的经验值,然后创建结算界面,并通知lua层结算信息(不要忘了结算时还要看是否处于连击状态,是的话要把连击数和积分更新)。而要显示队友的结算信息,还需要在TeamComponent中遍历场景中的所有controller,收集他们所持有的玩家信息,同步到lua。这个过程一定是在结算界面创建之后,lua只要接收到队友的信息,就在显示玩家信息的地方添加一行grid。
但如果结算时只拿现场player的数据,就无法显示之前掉线了的玩家的信息,要解决这个问题还需要在玩家掉线时在本地存一份他的数据,结算时一并显示。 这导致另一个问题,由于客户端取属性有延迟,如果服务器刚刚改过这个属性,客户端还没接收到改动的同步,拿到的就是老的数据,还需要在属性变动后通知lua,修正为正确的值。这里就有两种选择,一是监听主角的最大连击、分数等属性,如果有变动就广播,结算界面监听实时更新;二是等待服务器通知玩家信息,这样可以保证下发的信息是最新的,但是这样会有等级没加上去的情况。综合考虑还是选第二种做法,只是结算界面显示一下没必要特地去监听一个属性。
经验条动画(2021.1.10)
玩家这次获得的经验会同步到ui,读表可知每级升级所需的经验,那么根据玩家的等级就可以知道升下一级所需经验,和当前经验相加,如果比所需经验多,那么就说明升级了,要播放一个从(CurExp/NeededExp, 1)的进度条动画,然后就是继续查下一等级所需经验,视经验多少播放(0, 1)或(0, Exp/NeededExp)的动画。思路是处理数据的时候把每次进度条涨的动画抽象成两个数StartPoint、EndPoint,遍历经验表时计算出当前等级的StartPoint和EndPoint,存进一个队列,直到不再升级,遍历结束后维护一个timer,提取队列中的数据,播放动画,直到队列为空。
场景中人物拿枪(2020.12.30)
在蓝图中spawn出对应的Actor,然后找到场景中的人物蓝图,拿到Mesh,调用AttachToComponent,加到对应的Socket上。Mesh骨骼上的socket可以让其他物件attach上去,之后就可以跟随着它的动画运动。
大厅人物模型动画性别区分(2021.1.14)
大厅中有一个Actor蓝图,其中的SkeletalMeshComponent使用女性Mesh时,要同步播放女性的idle和rest动画,动画使用的是Mesh中引用的动画蓝图,动画蓝图中使用BlendPosesBybool和一个bool变量判断应该播放哪个idle动画,在CharacterPlayer中定义一个delegate用来通知,在选择界面,当选中不同模型icon时,lua通知PlayerState选中的modelid,根据id读表得到对应的SK路径,替换Mesh中的SkeletalMesh,这是换模型的流程,现在只需要加一个通知前面定义的delegate就行,然后在人物蓝图中bind这个delegate,一旦接到通知就刷新性别,动画就会切换。除此之外还需要一个打断rest蒙太奇的逻辑,每过一段随机的时间人物会从idle转到播放rest动画,这通过动画创建的montage实现,在动画蓝图中创建了slot,montage激活时就会进入。如果不打断,人物进入了rest动画,此时点击切换模型,模型更换,但是还在播放之前的montage动画,如果性别不同会很违和。
从改Mesh改为重新创建Actor(2021.1.20)
由于出现疑似由更换Mesh导致的内存资源暴涨,需要把换模型从换Mesh改成直接换Actor,把之前场景中的展示人物蓝图作为父类,在派生的子类中更换Mesh。每次要换模型的时候先找到原来的Actor(用标签来检索),保存它的Transform,然后尝试删除,如果删除成功,则根据表格中的路径LoadClass,然后根据前面的Transform调用SpawnActor即可。
直接换Mesh还有一个问题,部分模型身上有一些小的挂件是Actor单独的MeshComponent,如果只更换人物的Mesh还要根据id去隐藏或显示相应的子Mesh,而直接根据蓝图生成Actor就没有这个问题。
手雷伤害计分(2021.1.15)
为积分专门写了一个handle类,所有伤害都触发这个handle统一去计算分数,现在要把手雷的爆炸伤害也并入。先找到手雷爆炸伤害对应的GE蓝图,在GameEffect中的Execution中加入我们定义好的handle类即可。在显示时需要区分击杀方式,由于手雷伤害GE是instant,不好直接给npc上tag,而要在GE中的GameplayEffectAssetTags为自己定义一个tag,在handle中处理时去判断该GE上有没有手雷伤害的tag就行了。
这部分用到了GameplayAbilitySystem,正在写一个专门的GAS总结,目前还在看文档整理。
交互物品触发器框架
待梳理
delegate通知实现
目前是根据要传递的参数个数,使用不同的宏来声明delegate,然后再在类中定义,也不用初始化,在需要的时候调用Broadcast就行。宏的实现还需要时间细看总结。
Bug合集
- go服务器收不到客户端发过去的消息请求,排查后发现是客户端请求的接口UFUNCTION带了Server标签
- 连击结束后播放几秒钟的结束动画显示此次连击的积分,若是此时再次触发连击,则连击UI在后面都不可见了,原因是隐藏UI的动画被新动画打断,而隐藏动画修改的可见性没有恢复,所以看不见了,只需在播放隐藏动画的时候设置一个标志位flag,只要flag为true就不播放其他动画,隐藏动画播放完毕后再检查需要播放哪些动画。
- HUD上的按钮点击即使没有触发逻辑,点击后依然会打断其他输入,如移动,原因是Button上的IsFocusable选项没有勾掉
- ui上的text用SetText或bind变量没有生效,蓝图断点也进了,原因是当初把这个umg做成可以复用的类型,同时在hud上存在三个,根据配置不同显示不同属性,hud上配置出错,因此进的断点是另一个ui实例,没有立刻发现是因为这三个ui不会同时出现,为这个问题浪费了宝贵的半小时
- 掉落疑似偶尔出现显示不存在道具的bug,相关逻辑有点乱,需要重构
- 模型选择时有一个模型SK找不到资源,只在手机上出现,怀疑是资源没打进去,查reference果然,没有东西引用过它,打包的时候没打进去
- 大厅ui在重新登录后发生部分ui重复创建的问题,查代码后发现该ui是RPC通知创建的,之前注册RPC回调的ui没有删干净,导致残留多个回调,重复创建ui;后面发现即使使用RemoveAllWidgets来清理ui,RPC回调逻辑依然残留,要手动清理