UE-AIAE案例02-xLua To Unreal C++
<p>[TOC]</p>
<table>
<thead>
<tr>
<th>作者</th>
<th>QFord</th>
</tr>
</thead>
<tbody>
<tr>
<td>更新日期</td>
<td>2024-6-15</td>
</tr>
</tbody>
</table>
<h1>背景</h1>
<p>此案例来源于<strong>virtual-lab-unreal</strong>项目,原有小磁针在磁场下的偏转表现的核心算法是基于xLua实现的。
当然,背后调用的API也有涉及到Unity的。</p>
<h1>需求</h1>
<p>在Unreal中实现小磁针在磁场中的偏转表现。</p>
<h1>方案</h1>
<p>我们将通过AI提供的能力,将相关的xLua代码转换为C++代码,然后再通过人工进行调试。
此外,原先此表现是直接放置在器材中,而Unreal中则是抽象为组件。</p>
<h1>小磁针-xLua代码</h1>
<pre><code class="language-lua">--根据磁场更新角度
function XiaoCiZhenAgent:UpdateRotate(deltaTime)
if not self.CiZhenPath then return end
local nowPos = self.Transform:GetChildPosition(self.CiZhenPath,false)
local magVec = self.MagneticEngine:GetMagneticField(nowPos)
local length = self:GetModule(magVec)
if not length or length == 0 then
magVec = Vector3.forward
end
local selfVec_LocalY = self:GetElementLocalVector(DirectionType.Up)
local targetVec= ToolClass.GetProjectionPos_PointPlane(magVec,Vector3.zero,selfVec_LocalY)
local cizhenVec = self:GetElementLocalVector(DirectionType.Forward,self.ModelPath[&quot;CiZhenParent&quot;])
local isTargerChange=false
if self.saveTargetVec~=targetVec then
self.saveTargetVec=targetVec
isTargerChange=true
end
--不动
if math.rad(Vector3.Angle(cizhenVec,targetVec))==0 then
self.shakeTargetVector=nil
return
end
--获取晃动后的目标向量
if isTargerChange or self.shakeTargetVector==nil or math.rad(Vector3.Angle(cizhenVec,self.shakeTargetVector))&lt;0.01 then
self.rotateDir=Vector3.Cross(cizhenVec,targetVec).y&lt;0 and -1 or 1
local axis=self.rotateDir&gt;0 and selfVec_LocalY or -selfVec_LocalY
local q=Quaternion.AngleAxis(Vector3.Angle(cizhenVec,targetVec)*1.5,axis)
self.shakeTargetVector=q*cizhenVec
end
local newDirection = Vector3.RotateTowards(cizhenVec, self.shakeTargetVector, 0.12, deltaTime);
local angle=math.rad(Vector3.Angle(cizhenVec,newDirection))
--旋转的deltaAngle
if self.rotateDir&lt;0 then
angle=-angle
end
--旋转
self.VLabSlideRotateCtrl:Slide(self.rotatorId,angle)
end</code></pre>
<h1>小磁针-xLua代码->Unreal C++</h1>
<p>主要是基于Cursor-GPT4o模型生成,只要打开相关的器材的Lua文件,选定函数后提示词直接用:转换为Unreal C++实现即可,转换后的代码如下:
> 我所使用的Cursor是付费版本的,在大部分情况下,生成的速度相当快。当然,大家也可以使用公司免费提供的工具,比如工程院的AI Hub服务。</p>
<pre><code class="language-cpp">void UMagneticNeedleComponent::UpdateRotate(float DeltaTime)
{
if (CiZhenPath.IsEmpty()) return;
//1:获取磁针节点的世界坐标
FVector NowPos = GetChildPosition(CiZhenPath, true);
//2: 获取当前位置的磁场强度和方向
FVector MagVec = GetMagneticField(NowPos);
//3: 取得向量模长
float Length = MagVec.Size();
if (Length == 0)
{
MagVec = FVector::ForwardVector;
}
FVector SelfVec_LocalY = GetElementLocalVector(FVector::UpVector, ModelPath[&quot;CiZhenParent&quot;]);
FVector TargetVec = GetProjectionPos_PointPlane(MagVec, FVector::ZeroVector, SelfVec_LocalY);
FVector CiZhenVec = GetElementLocalVector(FVector::ForwardVector, ModelPath[&quot;CiZhenParent&quot;]);
bool IsTargetChange = false;
if (SaveTargetVec != TargetVec)
{
SaveTargetVec = TargetVec;
IsTargetChange = true;
}
if (FMath::RadiansToDegrees(FVector::DotProduct(CiZhenVec, TargetVec)) == 0)
{
ShakeTargetVector = FVector::ZeroVector;
return;
}
if (IsTargetChange || ShakeTargetVector.IsZero() || FMath::RadiansToDegrees(FVector::DotProduct(CiZhenVec, ShakeTargetVector)) &lt; 0.01f)
{
RotateDir = FVector::CrossProduct(CiZhenVec, TargetVec).Y &lt; 0 ? -1 : 1;
FVector Axis = RotateDir &gt; 0 ? SelfVec_LocalY : -SelfVec_LocalY;
FQuat Q = FQuat(Axis, FMath::DegreesToRadians(FVector::DotProduct(CiZhenVec, TargetVec) * 1.5f));
ShakeTargetVector = Q.RotateVector(CiZhenVec);
}
FVector NewDirection = FMath::VInterpTo(CiZhenVec, ShakeTargetVector, DeltaTime, 0.12f);
float Angle = FMath::RadiansToDegrees(FVector::DotProduct(CiZhenVec, NewDirection));
if (RotateDir &lt; 0)
{
Angle = -Angle;
}
Slide(RotatorId, Angle);
}</code></pre>
<p>> 其中xLua自定义的函数也会生成对应的空的C++函数,这些函数内容比较多,将在下面展示。
虽然实现是空的,但是接着还可以通过AI来一个个实现。
<strong>注意:</strong>xLua的自定义函数有的是自行lua实现的,有的是调用Unity实现的。
这块一般需要人工找到源码在哪,再通过AI来转换。
一般说来,命名良好的代码,通过AI转换的成功率会高很多。
此外,上述的代码是调试前的。</p>
<h1>GetChildPosition 获取子节点坐标</h1>
<p>lua中的此方法主要是通过Unity组件的扩展方法实现的</p>
<h2>UE C++代码</h2>
<pre><code class="language-cpp">FVector UUTransformUtils::GetChildPosition(AActor* Actor, FString ChildPath, bool bWorldSpace)
{
if (!Actor)
{
return FVector::ZeroVector;
}
USceneComponent* ChildComponent = FindComponentByPath&lt;USceneComponent&gt;(Actor, ChildPath);
if (ChildComponent)
{
if (bWorldSpace)
{
return ChildComponent-&gt;GetComponentLocation();
}
else
{
return ChildComponent-&gt;GetRelativeLocation();
}
}
return FVector::ZeroVector;
}</code></pre>
<pre><code class="language-cpp">template &lt;typename T&gt;
T* UUTransformUtils::FindComponentByPath(AActor* Actor, const FString&amp; Path)
{
TArray&lt;FString&gt; PathComponents;
Path.ParseIntoArray(PathComponents, TEXT(&quot;/&quot;));
if (PathComponents.Num() == 0)
{
UE_LOG(LogTemp, Warning, TEXT(&quot;PathComponents.Num = %d&quot;), 0);
return nullptr;
}
USceneComponent* CurrentComponent = Actor-&gt;GetRootComponent();
for (const FString&amp; ComponentName : PathComponents)
{
if (!CurrentComponent)
{
return nullptr;
}
TArray&lt;USceneComponent*&gt; ChildComponents;
CurrentComponent-&gt;GetChildrenComponents(true, ChildComponents);
USceneComponent* NextComponent = nullptr;
for (USceneComponent* ChildComponent : ChildComponents)
{
if (ChildComponent-&gt;GetName() == ComponentName)
{
NextComponent = ChildComponent;
break;
}
}
CurrentComponent = NextComponent;
}
return Cast&lt;T&gt;(CurrentComponent);
}</code></pre>
<h1>ToolClass.GetProjectionPos_PointPlane 计算点到平面投影坐标</h1>
<h2>xLua代码</h2>
<pre><code class="language-lua">function ToolClass.GetProjectionPos_PointPlane(point,planePoint,planeNormalVec)
local tValue_formula = ((planeNormalVec.x*planePoint.x+planeNormalVec.y*planePoint.y+
planeNormalVec.z*planePoint.z)-(planeNormalVec.x*point.x+planeNormalVec.y*point.y+planeNormalVec.z*point.z))/
(planeNormalVec.x^2+planeNormalVec.y^2+planeNormalVec.z^2)
return Vector3(point.x+planeNormalVec.x*tValue_formula,point.y+planeNormalVec.y*tValue_formula,
point.z+planeNormalVec.z*tValue_formula)
end</code></pre>
<h2>UE C++代码</h2>
<p>UE中已经有内置上述的GetProjectionPos_PointPlane方法,可以直接问AI,下面是部分回答:
在 Unreal 引擎中,已经有内置的函数可以实现将点投影到平面上的功能。这个函数是 FVector::PointPlaneProject。它可以通过给定一个点、平面上的一个点和平面的法向量来计算点在平面上的投影。
示例代码:
<code>FVector ProjectedPoint = FVector::PointPlaneProject(Point, PlaneBase, PlaneNormal);</code></p>
<p>基于AI的回答和先前AI生成的空C++函数,我做了补充:</p>
<pre><code class="language-cpp">FVector UMagneticNeedleComponent::GetProjectionPos_PointPlane(FVector Point, FVector PlaneOrigin, FVector PlaneNormal)
{
// Implement this function to get the projection of a point onto a plane
return FVector::PointPlaneProject(Point,PlaneOrigin,PlaneNormal);
}</code></pre>
<h1>GetElementLocalVector 获取器材的本地向量</h1>
<h2>xLua代码</h2>
<pre><code class="language-cpp">function PhysicEleCommonBaseLogic:GetElementLocalVector(axisType,elementOriPath,elementOriPos,element)
if not element then element = self end
if not elementOriPath then elementOriPath = EmptyChildPath end
if not elementOriPos then
if elementOriPath==EmptyChildPath then
elementOriPos = element.Transform:GetPosition()
else
elementOriPos = element.Transform:GetChildPosition(elementOriPath,false)
end
end
local elementTargetPos
if axisType==DirectionType.Right then
elementTargetPos = element.Transform:TransformPoint(elementOriPath,1,0,0)
elseif axisType==DirectionType.Left then
elementTargetPos = element.Transform:TransformPoint(elementOriPath,-1,0,0)
elseif axisType==DirectionType.Up then
elementTargetPos = element.Transform:TransformPoint(elementOriPath,0,1,0)
elseif axisType==DirectionType.Down then
elementTargetPos = element.Transform:TransformPoint(elementOriPath,0,-1,0)
elseif axisType==DirectionType.Forward then
elementTargetPos = element.Transform:TransformPoint(elementOriPath,0,0,1)
elseif axisType==DirectionType.Back then
elementTargetPos = element.Transform:TransformPoint(elementOriPath,0,0,-1)
else return
end
return elementTargetPos - elementOriPos
end</code></pre>
<h2>UE C++代码</h2>
<pre><code class="language-cpp">FVector UUTransformUtils::GetElementLocalVector(
AActor* Element,
EDirectionType AxisType,
FString ElementOriPath,
FVector ElementOriPos)
{
if (!Element)
{
return FVector::ZeroVector;
}
if (ElementOriPos.IsZero())
{
ElementOriPos = Element-&gt;GetActorLocation();
}
USceneComponent* OriComponent = nullptr;
if (!ElementOriPath.IsEmpty())
{
OriComponent = FindComponentByPath&lt;USceneComponent&gt;(Element, ElementOriPath);
if (OriComponent)
{
ElementOriPos = OriComponent-&gt;GetComponentLocation();
}
}
else
{
OriComponent = Element-&gt;GetRootComponent();
ElementOriPos = OriComponent-&gt;GetComponentLocation();
}
FVector DirectionVector;
switch (AxisType)
{
case EDirectionType::Right:
DirectionVector = FVector::RightVector;
break;
case EDirectionType::Left:
DirectionVector = FVector::LeftVector;
break;
case EDirectionType::Up:
DirectionVector = FVector::UpVector;
break;
case EDirectionType::Down:
DirectionVector = FVector::DownVector;
break;
case EDirectionType::Forward:
DirectionVector = FVector::ForwardVector;
break;
case EDirectionType::Back:
DirectionVector = FVector::BackwardVector;
break;
}
FVector ElementTargetPos;
if (OriComponent)
{
ElementTargetPos = OriComponent-&gt;GetComponentTransform().TransformPosition(DirectionVector);
}
else
{
ElementTargetPos = Element-&gt;GetActorTransform().TransformPosition(DirectionVector);
}
return ElementTargetPos - ElementOriPos;
}</code></pre>
<h1>Unity坐标转UE坐标</h1>
<p>这个通过AI转换的时候,发现了错误,我们通过人工修正了</p>
<pre><code class="language-cpp">FVector UUTransformUtils::ConvertUnityToUnreal(const FVector&amp; UnityVector)
{
// Unity的(x, y, z) -&gt; Unreal的(x, z, -y)
// 上述这个AI提示是错的,应该是(x, z, y)
return FVector(UnityVector.Z, UnityVector.X, UnityVector.Y) * 100.0f;
}</code></pre>
<h1>编写测试代码-模拟磁场强度和方向的数据</h1>
<p>下面的min和max值是通过Unity版本确定的,通过输入注释,Copilot就能自动生成代码。</p>
<pre><code class="language-cpp">FVector UMagneticNeedleComponent::GetMagneticField(FVector Position)
{
// Implement this function to get the magnetic field vector at the given position
//返回一个随机的世界坐标向量值,用来表示磁场的方向,便于测试
/*
min: &quot;(-0.18, 0.00, 0.00)&quot;
max: &quot;(0.07, 0.97, 1.05)&quot;
*/
FVector min = UUTransformUtils::ConvertUnityToUnreal(FVector(-0.18f, 0.00f, 0.00f));
FVector max = UUTransformUtils::ConvertUnityToUnreal(FVector(0.07f, 0.97f, 1.05f));
return FVector(FMath::RandRange(min.X, max.X), FMath::RandRange(min.Y, max.Y), FMath::RandRange(min.Z, max.Z));
}</code></pre>
<h1>总结</h1>
<p>我们通过AI工具:GPT以及Copilot的帮助,快速并成功地将xLua代码转换为Unreal C++代码。
这块对于效率提升帮助很大,特别是对于UE C++的新手来说。
当然,对于AI生成的代码,跟我们开发一样,也会出现缺陷和问题,这时候就需要我们自己进行调试和完善。
最后,也期待大家多多利用AI来协助编码,分享更多更为有趣的案例。</p>