目录
前言
最近有做到一个需求。
某个 Panel 放大,最大的放大倍数都不满足项目的需求。
所以需要自定义放大倍数,把放大倍数放大。
网上查了很多资料。很少有记录这样的需求。相关资源都很少。
UE 源码配合 Google 才能初窥门径。
UE 源码查看
在 Google 上搜索:UE5 modify ZoomLevel
SNodePanel::ZoomLevels
发现源码是在 SNodePanel.h 文件中,点进去查看(我下载的UE源码版,没有的小伙伴可以自行去 GitHub 拉下来)
实际项目 Debug,鼠标滚轮放大之后,会跑到这个函数里面来。
float SNodePanel::GetZoomAmount() const
{
if (bAllowContinousZoomInterpolation)
{
return FMath::Lerp(ZoomLevels->GetZoomAmount(PreviousZoomLevel), ZoomLevels->GetZoomAmount(ZoomLevel), ZoomLevelGraphFade.GetLerp());
}
else
{
return ZoomLevels->GetZoomAmount(ZoomLevel);
}
}
我们来看看 ZoomLevels 是什么东西。
SNodePanel.h
ZoomLevels:用于将 ZoomLevel 值映射到实际节点缩放值的接口。
ZoomLevels 官方文档
发现他是 struct FFixedZoomLevelsContainer 类型,点进去看看。
FFixedZoomLevelsContainer::ZoomLevels
发现一个成员变量:ZoomLevels。
构造函数里面里面初始化了 ZoomLevels 的一些信息。
这里需要区分下两个 ZoomLevels。
- SNodePanel::ZoomLevels
- FFixedZoomLevelsContainer::ZoomLevels
问:那么这两 ZoomLevels 是怎么联系起来的呢?
两 ZoomLevels 之间的联系
我们先来看 SNodePanel 的 Construct 函数(Construct 用于初始化成员变量)
发现这里初始化 SNodePanel::ZoomLevels 是调用了 SetZoomLevelsContainer。
SetZoomLevelsContainer 是一个模板函数。
这里我们可以看出来,如果 SNodePanel::ZoomLevels 没有被初始化,那么就会构造一个 FFixedZoomLevelsContainer 类型的 ZoomLevels。
所以还是没看出来他们有什么联系,我们先继续往下分析。
FZoomLevelsContainer
我们回过头来看 FFixedZoomLevelsContainer。
struct FFixedZoomLevelsContainer : public FZoomLevelsContainer
他是继承自 FZoomLevelsContainer,我们点进去看看。
/**
* Interface for ZoomLevel values
* Provides mapping for a range of virtual ZoomLevel values to actual node scaling values
*/
struct FZoomLevelsContainer
{
/**
* @param InZoomLevel virtual zoom level value
*
* @return associated scaling value
*/
virtual float GetZoomAmount(int32 InZoomLevel) const = 0;
/**
* @param InZoomAmount scaling value
*
* @return nearest ZoomLevel mapping for provided scale value
*/
virtual int32 GetNearestZoomLevel(float InZoomAmount) const = 0;
/**
* @param InZoomLevel virtual zoom level value
*
* @return associated friendly name
*/
virtual FText GetZoomText(int32 InZoomLevel) const = 0;
/**
* @return count of supported zoom levels
*/
virtual int32 GetNumZoomLevels() const = 0;
/**
* @return the optimal(1:1) zoom level value, default zoom level for the graph
*/
virtual int32 GetDefaultZoomLevel() const = 0;
/**
* @param InZoomLevel virtual zoom level value
*
* @return associated LOD value
*/
virtual EGraphRenderingLOD::Type GetLOD(int32 InZoomLevel) const = 0;
// Necessary for Mac OS X to compile 'delete <pointer_to_this_object>;'
virtual ~FZoomLevelsContainer( void ) {};
};
看下来可以理解为它是一个接口,也就是其它语言中的 Interface。
函数分析
拿 FFixedZoomLevelsContainer 做例子,我们应该很容易分析出 FZoomLevelsContainer 各个函数的意义。
先解释几个变量
- ZoomLevel:放大级别
- ZoomAmount: 具体放大的倍数
然后再解释下其中几个函数的意义
// 给一个 ZoomLevel 返回对应的 ZoomAmount
virtual float GetZoomAmount(int32 InZoomLevel) const = 0;
// 给一个 ZoomAmount 返回一个最接近的 ZoomLevel
virtual int32 GetNearestZoomLevel(float InZoomAmount) const = 0;
// 给一个 ZoomLevel 返回对应的显示文本
virtual FText GetZoomText(int32 InZoomLevel) const = 0;
// 返回一共有多少的 放大级别
virtual int32 GetNumZoomLevels() const = 0;
// 返回默认的放大级别(用于初始化)
virtual int32 GetDefaultZoomLevel() const = 0;
// 返回对应 ZoomLevel 的 LOD 级别
virtual EGraphRenderingLOD::Type GetLOD(int32 InZoomLevel) const = 0;
// 析构函数
virtual ~FZoomLevelsContainer( void ) {};
所以实际上,两个 ZoomLevels 并没有实际的联系,只是为了 GetZoomAmount 方便返回对应的 ZoomAmount,提前在 FFixedZoomLevelsContainer 里面写死了对应的放大级别。
所以这里其实也可以用一些数学方法在 Runtime 的时候动态计算 ZoomAmount。
结论
所以只要我们自己写一个 struct 继承自 FZoomLevelsContainer,重写里面的函数,然后在自己的 Panel 类里面提前初始化一下 SNodePanle::ZoomLevels 就可以自己控制放大级别了。
如果不提前初始化 ZoomLevels,那么 SNodePanle::Construct 就会自动初始化一个 FFixedZoomLevelsContainer 类型的 ZoomLevels(上文有提及)
具体实现
- 自定义一个 ZoomLevelsContainer 继承自 FZoomLevelsContainer
具体有多种办法实现对应的纯虚函数。
我这里没有像 FFixedZoomLevelsContainer 一样自己包了一个 ZoomLevles ,而是通过数学计算出对应的 ZoomLevel 对应的 ZoomAmount。
建议读者根据实际项目去重写。
struct FCustomPanelZoomLevelsContainer
: public FZoomLevelsContainer
{
virtual float GetZoomAmount(int32 InZoomLevel) const override
{
return 1.f / FMath::Square(GetNumZoomLevels() - InZoomLevel + 1);
}
virtual int32 GetNearestZoomLevel(float InZoomAmount) const override
{
for (int32 ZoomLevelIndex = 0; ZoomLevelIndex < GetNumZoomLevels(); ++ZoomLevelIndex)
{
if (InZoomAmount <= GetZoomAmount(ZoomLevelIndex))
{
return ZoomLevelIndex;
}
}
return GetDefaultZoomLevel();
}
virtual FText GetZoomText(int32 InZoomLevel) const override { return FText::AsNumber(GetZoomAmount(InZoomLevel));}
virtual int32 GetNumZoomLevels() const override { return 300;}
virtual int32 GetDefaultZoomLevel() const override { return GetNumZoomLevels() - 10; }
virtual EGraphRenderingLOD::Type GetLOD(int32 InZoomLevel) const override {return EGraphRenderingLOD::DefaultDetail;}
};
- 你肯定有一个自定义的 Panle 类继承自 SNodePanel,比如叫做 SMyPanle。
class SMyPanle: public SNodePanel
{
public:
SMyPanle();
//Some members
void Construct()
{
ZoomLevels = MakeUnique<FCustomPanelZoomLevelsContainer>();
// 一定要在 SNodePanel::Construct 之前进行 ZoomLevels 的初始化
SNodePanel::Construct();
}
}
- 在其他业务代码里创建这个 Panle 之后,在 UE 编辑器上进行放大就能看出效果了~可以试着自己改改对应的重写函数来看看具体效果。
验证
我这里直接加一个 UE_LOG 看看有没有被调用。
virtual float GetZoomAmount(int32 InZoomLevel) const override
{
UE_LOG(LogTemp, Error, TEXT("GetZoomAmount: InZoomLevel %d"), InZoomLevel);
return 1.f / FMath::Square(GetNumZoomLevels() - InZoomLevel + 1);
}
按道理,正常放大的时候,会调用 GetZoomAmount。
一进入 Panel 就会一直刷。此时打印的是 290.
因为我的 GetDefaultZoomLevel
virtual int32 GetNumZoomLevels() const override { return 300;}
virtual int32 GetDefaultZoomLevel() const override { return GetNumZoomLevels() - 10; }
所以默认返回的 300 - 10 = 290
然后使用鼠标滚轮滑动一下。
- 缩小
- 放大
然后发现放大到 290 无法进一步放大了。
这个时候我们按住 Ctrl 再去放大。
就会发现现在可以最大放大到 299
这是因为我们的 GetNumZoomLevels 返回的是 300,所以最大只能 299.
为什么不是 300 而是 299,因为是从 0 开始。