原由:需要理解异性控件控制,一般的图像控件都是方形的,由于一些需要,做成像摇杆类似的控件控制,将摇杆限制在一个圆内,并且返回对应值。
源码不多,通俗易理解,每个变量作用都有注释
//绘制摇杆两个圆形的TICK函数
void SVirtualJoystick::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
// 状态判定,是否显示摇杆图像
if (State == State_WaitForStart || State == State_CountingDownToStart)
{
CurrentOpacity = 0.f;
}
else
{
// lerp to the desired opacity based on whether the user is interacting with the joystick
// 根据用户是否与操纵杆交互,调整到所需的不透明度(翻译)
// CurrentOpacity默认值为0.1f
// GetBaseOpacity()如果激活摇杆时激活状态,则返回1.0f。否则0.1f
// 插值类型为Tick每帧时间间隔
CurrentOpacity = FMath::Lerp(CurrentOpacity, GetBaseOpacity(), OPACITY_LERP_RATE * InDeltaTime);
}
// 以上这个IF设置当前不透明的值
---*---
// count how many controls are active
// 计算有多少控件处于活动状态
int32 NumActiveControls = 0;
// figure out how much to scale the control sizes
// 计算控件大小的缩放比例 --->1
float ScaleFactor = GetScaleFactor(AllottedGeometry);
// 两个摇杆则循环两次
for (int32 ControlIndex = 0; ControlIndex < Controls.Num(); ControlIndex++)
{
//获取摇杆信息
FControlData& Control = Controls[ControlIndex];
// 更新中心位置,则摇杆跟着拇指位置走
if (Control.bNeedUpdatedCenter)
{
// 增加摇杆启动的时间
Control.ElapsedTime += InDeltaTime;
// 当启动时间大于延迟激活时(未激活时)
if (Control.ElapsedTime > ActivationDelay)
{
Control.bNeedUpdatedCenter = false;
CurrentOpacity = ActiveOpacity;
// 如果剧中
if (!bPreventReCenter)
{
// 设置摇杆对中的位置
Control.VisualCenter = Control.NextCenter;
}
// 触摸事件
HandleTouch(ControlIndex, Control.NextCenter, AllottedGeometry.GetLocalSize());
}
}
// calculate absolute positions based on geometry
// @todo: Need to manage geometry changing!
//基于几何计算绝对位置
//@todo:需要管理几何变化!
// 不需要根据几何图形定位控件 or 当前缩放值不等于上次使用缩放值 --->1
if (!Control.bHasBeenPositioned || ScaleFactor != PreviousScalingFactor)
{
// 获得摇杆的信息(图片、透明度设置等)
const FControlInfo& ControlInfo = Control.Info;
// update all the sizes
// 校正的实际控制中心
Control.CorrectedCenter = FVector2D(ResolveRelativePosition(ControlInfo.Center.X, AllottedGeometry.GetLocalSize().X, ScaleFactor), ResolveRelativePosition(ControlInfo.Center.Y, AllottedGeometry.GetLocalSize().Y, ScaleFactor));
// 重新对中位置
Control.VisualCenter = Control.CorrectedCenter;
// 可在交互大小区域内重新居中的操纵杆的校正大小 --->操纵杆
Control.CorrectedVisualSize = FVector2D(ResolveRelativePosition(ControlInfo.VisualSize.X, AllottedGeometry.GetLocalSize().X, ScaleFactor), ResolveRelativePosition(ControlInfo.VisualSize.Y, AllottedGeometry.GetLocalSize().Y, ScaleFactor));
// 中心周围可交互区域的校正大小
Control.CorrectedInteractionSize = FVector2D(ResolveRelativePosition(ControlInfo.InteractionSize.X, AllottedGeometry.GetLocalSize().X, ScaleFactor), ResolveRelativePosition(ControlInfo.InteractionSize.Y, AllottedGeometry.GetLocalSize().Y, ScaleFactor));
// 可以在交互大小区域内重新居中的拇指的校正大小 --->拇指
Control.CorrectedThumbSize = FVector2D(ResolveRelativePosition(ControlInfo.ThumbSize.X, AllottedGeometry.GetLocalSize().X, ScaleFactor), ResolveRelativePosition(ControlInfo.ThumbSize.Y, AllottedGeometry.GetLocalSize().Y, ScaleFactor));
// 控制输入的校正刻度
Control.CorrectedInputScale = ControlInfo.InputScale; // *ScaleFactor;
// 是否需要根据几何图形定位控件
Control.bHasBeenPositioned = true;
}
// 有交互 或者 向下一个TICK发送“释放”事件(可能是UNHandled或者Handled)
if (Control.CapturedPointerIndex >= 0 || Control.bSendOneMoreEvent)
{
Control.bSendOneMoreEvent = false;
// Get the corrected thumb offset scale (now allows ellipse instead of assuming square)
// 获得正确的拇指偏移比例(现在允许椭圆而不是假设正方形)--->重点来了!
// 2D向量:值为((拇指的X位置*2/摇杆的位置X),(拇指的Y位置*2/摇杆的位置Y))
FVector2D ThumbScaledOffset = FVector2D(Control.ThumbPosition.X * 2.0f / Control.CorrectedVisualSize.X, Control.ThumbPosition.Y * 2.0f / Control.CorrectedVisualSize.Y);
// 拇指平方和(x*x+y*y)
float ThumbSquareSum = ThumbScaledOffset.X * ThumbScaledOffset.X + ThumbScaledOffset.Y * ThumbScaledOffset.Y;
// 拇指平方根
float ThumbMagnitude = FMath::Sqrt(ThumbSquareSum);
// 拇指Norm值
FVector2D ThumbNormalized = FVector2D(0.f, 0.f);
// 一般不为假,除非手指大过屏幕
if (ThumbSquareSum > SMALL_NUMBER)
{
// 限制比例1以内
const float Scale = 1.0f / ThumbMagnitude;
ThumbNormalized = FVector2D(ThumbScaledOffset.X * Scale, ThumbScaledOffset.Y * Scale);
}
// Find the scale to apply to ThumbNormalized vector to project onto unit square
// 找到要应用于ThumbNormalized向量的比例,以投影到单位正方形上
//同样开平方根
float ToSquareScale = fabs(ThumbNormalized.Y) > fabs(ThumbNormalized.X) ? FMath::Sqrt((ThumbNormalized.X * ThumbNormalized.X) / (ThumbNormalized.Y * ThumbNormalized.Y) + 1.0f)
: ThumbNormalized.X == 0.0f ? 1.0f : FMath::Sqrt((ThumbNormalized.Y * ThumbNormalized.Y) / (ThumbNormalized.X * ThumbNormalized.X) + 1.0f);
// Apply proportional offset corrected for projection to unit square
// 将投影校正的比例偏移应用于单位平方
FVector2D NormalizedOffset = ThumbNormalized * Control.CorrectedInputScale * ThumbMagnitude * ToSquareScale;
// now pass the fake joystick events to the game
// 现在将虚拟操纵杆事件传递给游戏
const FGamepadKeyNames::Type XAxis = (Control.Info.MainInputKey.IsValid() ? Control.Info.MainInputKey.GetFName() : (ControlIndex == 0 ? FGamepadKeyNames::LeftAnalogX : FGamepadKeyNames::RightAnalogX));
const FGamepadKeyNames::Type YAxis = (Control.Info.AltInputKey.IsValid() ? Control.Info.AltInputKey.GetFName() : (ControlIndex == 0 ? FGamepadKeyNames::LeftAnalogY : FGamepadKeyNames::RightAnalogY));
// 设置焦点为活动窗口
FSlateApplication::Get().SetAllUserFocusToGameViewport();
// 传递控制值(值是轴映射)
FSlateApplication::Get().OnControllerAnalog(XAxis, 0, NormalizedOffset.X);
FSlateApplication::Get().OnControllerAnalog(YAxis, 0, -NormalizedOffset.Y);
}
// is this active?
if (Control.CapturedPointerIndex != -1)
{
// 记录交互的变量
NumActiveControls++;
}
}
// we need to store the computed scale factor so we can compare it with the value computed in the following frame and, if necessary, recompute widget position
//我们需要存储计算出的比例因子,以便将其与下一帧中计算出的值进行比较,并在必要时重新计算小部件位置
// 下面就不分析了
PreviousScalingFactor = ScaleFactor;
// STATE MACHINE!
if (NumActiveControls > 0 || bPreventReCenter)
{
// any active control snaps the state to active immediately
State = State_Active;
}
else
{
switch (State)
{
case State_WaitForStart:
{
State = State_CountingDownToStart;
Countdown = StartupDelay;
}
break;
case State_CountingDownToStart:
// update the countdown
Countdown -= InDeltaTime;
if (Countdown <= 0.0f)
{
State = State_Inactive;
}
break;
case State_Active:
if (NumActiveControls == 0)
{
// start going to inactive
State = State_CountingDownToInactive;
Countdown = TimeUntilDeactive;
}
break;
case State_CountingDownToInactive:
// update the countdown
Countdown -= InDeltaTime;
if (Countdown <= 0.0f)
{
// should we start counting down to a control reset?
if (TimeUntilReset > 0.0f)
{
State = State_CountingDownToReset;
Countdown = TimeUntilReset;
}
else
{
// if not, then just go inactive
State = State_Inactive;
}
}
break;
case State_CountingDownToReset:
Countdown -= InDeltaTime;
if (Countdown <= 0.0f)
{
// reset all the controls
for (int32 ControlIndex = 0; ControlIndex < Controls.Num(); ControlIndex++)
{
Controls[ControlIndex].Reset();
}
// finally, go inactive
State = State_Inactive;
}
break;
}
}
}
当前类还有个OnPaint函数,有机会在分析以下,但是我发现大多数使用了废弃函数,如果要复现起来也是很麻烦
(拉吉CSDN,发发发NM的发文助手)