虚拟实验室-Unreal 版本

虚拟实验室的Unreal 版本,第一个版本主要是以《探究通电螺线管外部的磁场分布》颗粒为例,设计和开发一个正式版本。


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[&amp;quot;CiZhenParent&amp;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))&amp;lt;0.01 then self.rotateDir=Vector3.Cross(cizhenVec,targetVec).y&amp;lt;0 and -1 or 1 local axis=self.rotateDir&amp;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&amp;lt;0 then angle=-angle end --旋转 self.VLabSlideRotateCtrl:Slide(self.rotatorId,angle) end</code></pre> <h1>小磁针-xLua代码-&gt;Unreal C++</h1> <p>主要是基于Cursor-GPT4o模型生成,只要打开相关的器材的Lua文件,选定函数后提示词直接用:转换为Unreal C++实现即可,转换后的代码如下: &gt; 我所使用的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[&amp;quot;CiZhenParent&amp;quot;]); FVector TargetVec = GetProjectionPos_PointPlane(MagVec, FVector::ZeroVector, SelfVec_LocalY); FVector CiZhenVec = GetElementLocalVector(FVector::ForwardVector, ModelPath[&amp;quot;CiZhenParent&amp;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)) &amp;lt; 0.01f) { RotateDir = FVector::CrossProduct(CiZhenVec, TargetVec).Y &amp;lt; 0 ? -1 : 1; FVector Axis = RotateDir &amp;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 &amp;lt; 0) { Angle = -Angle; } Slide(RotatorId, Angle); }</code></pre> <p>&gt; 其中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&amp;lt;USceneComponent&amp;gt;(Actor, ChildPath); if (ChildComponent) { if (bWorldSpace) { return ChildComponent-&amp;gt;GetComponentLocation(); } else { return ChildComponent-&amp;gt;GetRelativeLocation(); } } return FVector::ZeroVector; }</code></pre> <pre><code class="language-cpp">template &amp;lt;typename T&amp;gt; T* UUTransformUtils::FindComponentByPath(AActor* Actor, const FString&amp;amp; Path) { TArray&amp;lt;FString&amp;gt; PathComponents; Path.ParseIntoArray(PathComponents, TEXT(&amp;quot;/&amp;quot;)); if (PathComponents.Num() == 0) { UE_LOG(LogTemp, Warning, TEXT(&amp;quot;PathComponents.Num = %d&amp;quot;), 0); return nullptr; } USceneComponent* CurrentComponent = Actor-&amp;gt;GetRootComponent(); for (const FString&amp;amp; ComponentName : PathComponents) { if (!CurrentComponent) { return nullptr; } TArray&amp;lt;USceneComponent*&amp;gt; ChildComponents; CurrentComponent-&amp;gt;GetChildrenComponents(true, ChildComponents); USceneComponent* NextComponent = nullptr; for (USceneComponent* ChildComponent : ChildComponents) { if (ChildComponent-&amp;gt;GetName() == ComponentName) { NextComponent = ChildComponent; break; } } CurrentComponent = NextComponent; } return Cast&amp;lt;T&amp;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-&amp;gt;GetActorLocation(); } USceneComponent* OriComponent = nullptr; if (!ElementOriPath.IsEmpty()) { OriComponent = FindComponentByPath&amp;lt;USceneComponent&amp;gt;(Element, ElementOriPath); if (OriComponent) { ElementOriPos = OriComponent-&amp;gt;GetComponentLocation(); } } else { OriComponent = Element-&amp;gt;GetRootComponent(); ElementOriPos = OriComponent-&amp;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-&amp;gt;GetComponentTransform().TransformPosition(DirectionVector); } else { ElementTargetPos = Element-&amp;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;amp; UnityVector) { // Unity的(x, y, z) -&amp;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: &amp;quot;(-0.18, 0.00, 0.00)&amp;quot; max: &amp;quot;(0.07, 0.97, 1.05)&amp;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>

页面列表

ITEM_HTML