- 背包(2020.8.20)
- RPC(Romote Procedure Call)
- 掉落UI(2020.9.29)
- 上漂提示(2020.10.16)
- UE4纹理Atlas(2020.10.10)
- 受击环实现
- UI管理
- Bug合集
背包(2020.8.20)
需要从背包中获取某个类型的道具时,调用BackpackManager的get方法,其中最基础的是一个GetItems方法,它从外部接收一个函数,用来判断哪些道具应该被滤除,通过定义不同的匿名函数作为参数传入,可以很容易的实现GetItemByType和GetAllItems。
ue4中的蓝图如何在脚本中调用,变量如何调用修改?
通过编辑unlua生成的lua模板,控制其中组件的行为,如果组件也是蓝图,要获取其中的变量一样使用.
操作符
按钮如何注册回调? 对于一个Button组件,在脚本中为它的OnClicked事件注册回调函数。
如何显示、关闭界面? 显示需要拿到Widget蓝图后,用AddToViewport显示,关闭的话直接RemoveFromParent。
如何在umg中调整widget的各种属性? size和position这类在slot分类下,要调整这些属性先要将组件(如一个image)用Slot as Canvas Slot转换之后才能调整。 创建一个Vector2D使用MakeVector2D,要取xy的值用BreakVector2D。
如何读excel表? 先创建一个符合表数据类型的structure,然后import源文件,并按照我们创建的structure在引擎中生成data table,这是通常的做法。项目中使用了脚本将xslx文件生成为了一个和proto buffer格式一样的类,表中的数据转换成了类中的成员,每个excel就是一个类,然后C++层定义了根据表类型(也就是表名)去拿实例的方法,由于使用了模板,lua层不能直接调用,需要在C++层再封装一个方法给lua。
如何调用C++中模板函数? 在C++层再包一层方法,封装好需要使用的模板类型,直接返回需要的数据,lua层直接调这个方法。
这周(8.31)把美术拼的umg结构重新调整,以便根据需要去动态生成相应的ui。unlua中蓝图的Construct方法要在AddChild后才会调用。
读背包的数据应该由数据层脚本提供,ui层只调用,如果需要controller等比较常用的实例,应该通过Util等脚本的通用接口获取。(9.24)发现ui显示的数据需要从服务器获取而不是读表,只需要把数据层获取的实现改一下就行了,ui层不用动。
需求是选中武器点击装备后,该slot显示对应栏位的ui: 每次装备了道具或新增了道具服务器都会通知客户端刷新界面,刷新时先将显示grid的容器清空,再按服务器给的顺序添加,此时我们已经知道哪些道具已经被装备和其装备的栏位,只需要在创建grid时显示就行了。
属性界面要显示已装备和当前选中道具的对比,如果没有装备则直接显示选中的装备: 数据层负责拿属性数据,属性ui脚本负责刷新和显示,上层ui界面响应点击,那么判断是否需要显示对比的ui应该放在哪里去判断?想来想去感觉还是应该放到创建属性ui的方法中去判断,因为取当前选中道具数据的接口不应该关心现在装备了什么道具。 发现还是不行,之前设计生成属性ui的方法只关心数据而不关心是什么属性,所以要拿已装备武器的什么属性来对比对属性ui来说是未知的,还是得在数据层去取,然后填入属性数据中,属性ui刷新的时候去查看有没有需要对比的属性值(即已装备的值),来决定要不要进行对比,这样才比较合理。
十个按钮,响应的事件非常类似,只是根据不同种类的道具进入相应的界面,除了为每个按钮注册单独的方法特殊处理之外有更好的方法吗?(9.18) 封装一个蓝图widget,只包含一个按钮,然后自己定义一个event,为其定义需要传入的参数如index,然后将蓝图中定义好的变量在call时告知event。这样如果有很多按钮触发的事件很类似,就只用为它们注册同一个回调,然后根据传入的index执行不同的逻辑,减少冗余的代码。
显示若干行列的背包格子,使用grid panel。
RPC(Romote Procedure Call)
是一种客户端与服务器通信的方法,不同于传统的C->S、S->C基于协议收发消息,服务器在C++层定义了消息的结构和s->c、c->s的方法,客户端请求时是根据事先约定好的id去通知服务器去调用相应的方法,服务器调用结束后再将结果返回给客户端,客户端再通知umg或unlua中重写的方法去处理逻辑。
proto协议定义的接口回调函数中如何通知蓝图? 在controller中定义delegate,然后在回调函数中Broadcast。
两份代码,服务器一份客户端一份,要时刻区分代码是服务器请求客户端执行还是客户端请求服务器执行,举例来说客户端想同步一下ds上的数据,先用带有Server标签的UFUNCTION获取储存的数据,拿到后按自己想要的结构调用带有Client标签的UNFUNCTION,再在该函数中做表现。
官网中的定义为本地调用,但是远程执行(functions that are called locally, but executed remotely on another machine)。
掉落UI(2020.9.29)
在基类controller蓝图中定义事件,然后在脚本中进行覆写,子类controller触发时触发相应的事件,就会调用脚本中的事件了。由于基类已经实现了unlua 的GetModuleName接口,无法再为子类controller再创建一个脚本。
目前接收掉落信息的回调是在controller中处理,要达到玩家靠近显示拾取ui,超出范围隐藏的逻辑,就要维护一个timer,每次去检测掉落物的距离,这块之前写的比较急,有很多地方需要优化,而且有部分功能已经废弃,最好找时间重构一下(2021.1.21)。
有的动画播完之后要延迟几秒,然后再做别的事,这就需要动画播完之后触发一个事件,也可以直接去将动画调整成先渐入,然后保持一段时间最后渐隐的效果直接播放。
容器排序问题
由于UI中的元素根据物体在场景中与玩家的位置决定,因此加入UI的顺序是不确定的,即使我们将地面上的物体排好序储存起来,也不能保证其按顺序添加进入容器。但和平精英就有这个功能,新加进来的道具会按某种规则挤到之前有的道具之间,既然别人能实现我应该也能。
上漂提示(2020.10.16)
分别定义了提示的grid蓝图和有屏幕位置的container蓝图,显示的时候先创建container,再创建grid并将其添加进去,播放渐入渐出动画,动画播放完毕后销毁自身,同时销毁container,目前ui实例储存在controller上,出现的问题是可能有多个提示同时出现,如果用controller存储需要一个索引来区分每个container,然后用grid传传过来的参数找到对应的container删除,这样非常不优雅,以前不会出现多个同种ui所以没这个问题,要哪个ui实例直接拿就行了。解决方法很简单,在grid蓝图定义一个OnRemoved事件,container把grid加入时监听这个事件,在销毁grid时广播,container收到后也销毁自己。
grid中要求在渐入播放完毕若干秒后播放渐出,这可以通过在OnAnimationFinished中判断结束动画类型,然后用SetTimerDelegate实现,广播OnRemoved事件也在这里广播。
UE4纹理Atlas(2020.10.10)
ui中很多icon资源,目前美术是直接拖进引擎生成了texture,然后umg中创建image等组件去引用,这样未来肯定会影响游戏包的大小,用TexturePacker可以把若干散图打成一张整图texture,并用类似json的文件去储存每张散图的位置信息,根据它来生成sprite,提高储存效率,而在开发时如果每次打整图会影响效率,因此应该在打包时才去打整图,并将所有用到图片资源的地方都替换成整图生成的sprite。查资料时epic的一个开发人员提到这个可以自己写一个插件去实现,使用ue4中的commandline,但是没有提供具体的实现方法,这个后面还要看。
受击环实现
这个功能虽然不是我做的,但是实现方法应该是取玩家摄像头(不是角色)朝向,与规定半径范围内npc的夹角(yaw),在屏幕上创建初始朝向(0度)为向上的受击环ui,然后将其旋转之前算得的yaw角,并在tick或timer中根据npc位置实时更新,如果npc超出半径,则销毁或隐藏受击环。
UI管理
目前以类似链表的形式来管理多级UI,每生成一个次级UI,将上一级UI的实例储存到当前UI中并隐藏,在关闭当前UI后由此恢复上一层UI。
Bug合集
- 更改ui蓝图后依然调用了以前蓝图的unlua脚本,检查接口实现,没问题已经改过,后来发现原因是该ui在更改接口之前被加入了另一个umg中,相当于实例没有更新。
- 设置字体颜色的时候总是失败,按umg里的结构先创建了linear color,然后创建slate color,最后用slate color去SetColorAndOpacity,发现怎么设置都是默认的紫色,能想到的有两个原因,一是颜色创建有问题,二是颜色创建成功传入SetColorAndOpacity方法时有问题,排查后发现是slate color无法正常创建,其LinearColor成员SpecifiedColor始终没有被赋值,只能手动去覆盖(居然想了很久都没有想到这样去解决)。
- Guid作为道具的唯一id,有四个uint32成员ABCD,将其作为ui中grid的标识时发现拿到的不对,是因为
- 普通按钮发现点击时经常不能触发,排查后发现是没有设置图片时,Normal、Hovered和Pressed三个状态的Image Size没有一致,如果设置了图片则不会有这个问题。
- uuid中的uint32成员传到lua层经常变成负数,先以为是越界导致,但lua5.3已经支持了int64,没道理越界,回想起打断点看数据的时候数据似乎都是以16进制的形式从服务器发过来,因此推测数据其实没问题,只是lua将其以signed的形式解析了
- 后生成显示的UI阻挡了之前生成UI的按钮事件,之前没这个问题,后来发现是因为为了隐藏和显示该界面将其可见度设置成了visible,实际上canvas默认是HitNotTestable
- unlua脚本显示ui的方法,搬到另一个单独创建的脚本中就失效,不报错,日志都打出来了,创建的东西好像也没错,但就是看不见,调试后发现是worldcontext中GameInstance包含的LocalPlayers数组为空,导致创建widget后无法执行SetPlayerContext这一步。UWidgetBlueprintLibrary.Create(self, UE4.UClass.Load(path))的第一个参数,要传PlayerCharacter,而且要传进来直接用,不能存本地了再用。