关卡润色
这篇文章是这个解密游戏 demo 的最后一篇。在之前的文章中,所有的基本功能均已实现。在这篇文章中,我们只是增加一些关卡内容,让游戏更加丰富。
如图 1 所示,我们在关卡中添加了新内容。我们添加了一个石墩(SM_Statue_Stand)和一个金色的雕像(SM_Statue_Hooded_Metal)。这个金色的雕像就是我们最终想要带走的物体。我们还添加了一扇门(SM_Cell_Side)。

金色雕塑需要是可以抓取的对象,所以我们将其 碰撞预设 设置为 Custom,并使 Grabber 通道的碰撞响应方式为 阻挡。
因为此处扫掠检测针对的通道是自定义的 Grabber 通道,所以要自定义设置。
如果这部分知识点遗忘了的话,可以回顾文章 《几何体扫掠检测》。
石墩当作 Box 触发器。我们在石墩 Actor 上添加之前实现好的 Trigger 组件,并将允许触发的标签值(Acceptable Actor Tag)设置为“Unlock2”。金色雕像要可以触发,所以回到金色雕像的属性界面,添加“Unlock2”标签,并勾选 生成重叠事件。然后调节 Trigger 组件的大小和位置,让金色雕像可以在这个范围内触发。
触发移动的对象是这个房间里添加的门,所以我们在门 Actor 上添加之前实现的 Mover 组件。我们需要有物体重叠的时候,触发门向上移动打开。所以按照此效果设置 Mover 的编辑器蓝图参数(移动终点、移动时间)。
Trigger 组件和 Mover 组件设置好后,我们需要将其关联。此处学习一种和之前不一样的方式。我们点击 打开关卡蓝图。如图 2 所示,我们先获取门和石墩 Actor 的引用,然后调用 Get Component by Class 节点获取其下对应的组件。最后调用 SetMover 设置关联。
在场景编辑器上选中想要创建引用的 Actor,回到蓝图界面上右击,会自动显示创建该对象引用的选项。

不要忘记把金色雕像和门的 移动性 属性设置为 可移动。
我们想要石墩上有物体重叠的话,触发门向上打开;石墩上没有物体的话,门会回到原来关闭的状态。
如代码清单 1 所示,我们为了方便,选择尽量复用之前的代码。我们新增 ActiveTargetLocation 变量指示真正的终点:有物体重叠的时候,ActiveTargetLocation 是之前逻辑的 TargetLocation;没有物体重叠的时候,ActiveTargetLocation 设置为 InitialLocation,即回到初始位置。
同时为了防止 Mover 的 Tick 频繁调用,在到达目的地后,就关闭 Tick。
像 ShouldMove 这些原来的变量名,在新功能场景下,其实含义都发现变化了。为了方便不做修改了。
- // Called when the game starts
- void UMover::BeginPlay()
- {
- Super::BeginPlay();
- if (AActor* Owner = GetOwner())
- {
- InitialLocation = Owner->GetActorLocation();
- TargetLocation = InitialLocation + TargetOffset;
- CalculateMoveSpeed();
- ActiveTargetLocation = InitialLocation;
- }
- }
- // Called every frame
- void UMover::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
- {
- Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
- if (AActor* Owner = GetOwner())
- {
- FVector CurrentLocation = Owner->GetActorLocation();
- FVector NewLocation = FMath::VInterpConstantTo(
- CurrentLocation, ActiveTargetLocation, DeltaTime, MoveSpeed
- );
- Owner->SetActorLocation(NewLocation);
- // Stop moving
- if (FVector::Dist(NewLocation, ActiveTargetLocation) < 1.0f)
- {
- SetComponentTickEnabled(false);
- UE_LOG(LogTemp, Log, TEXT("UMover: %s reached target. Tick disabled."), *Owner->GetName());
- }
- }
- }
- void UMover::SetShouldMove(bool NewShouldMove)
- {
- ShouldMove = NewShouldMove;
- if (ShouldMove)
- ActiveTargetLocation = TargetLocation;
- else
- ActiveTargetLocation = InitialLocation;
- SetComponentTickEnabled(true);
- }
相应的,Trigger 的代码也做了一些修改。如代码清单 2 所示,我们新增 bWasAcceptingActor 变量,用来记录当前是否有物体和组件重叠。只有物体重叠状态发生了变化,才触发 Mover 移动,防止 Mover 被频繁调用。
- // Called every frame
- void UTriggerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
- {
- Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
- AActor* Actor = GetAcceptableActor();
- bool bHasActorNow = (Actor != nullptr);
- if (bHasActorNow != bWasAcceptingActor)
- {
- if (bHasActorNow)
- {
- UE_LOG(LogTemp, Display, TEXT("Unlocking"));
- UPrimitiveComponent* Component = Cast<UPrimitiveComponent>(Actor->GetRootComponent());
- if (Mover != NULL && Component != NULL)
- {
- Component->SetSimulatePhysics(false);
- Actor->AttachToComponent(this, FAttachmentTransformRules::KeepWorldTransform);
- Mover->SetShouldMove(true);
- }
- }
- else
- {
- UE_LOG(LogTemp, Display, TEXT("Relocking"));
- if (Mover != NULL)
- Mover->SetShouldMove(false);
- }
- }
- bWasAcceptingActor = bHasActorNow;
- }
现在的逻辑是,把金色雕像从石墩上拿走,门就会关闭,导致无法出去。应对的策略是,我们需要在石墩上放一个代替的物体。
我们在关卡的另一个角落里,添加一个罐子 SM_Pot_A_Body。把它当作金色雕像的替代,设置和金色雕像一样:碰撞预设 设置为 Custom,并使 Grabber 通道的碰撞响应方式为 阻挡;添加“Unlock2”标签;勾选 生成重叠事件;移动性 属性设置为 可移动。
最终的游戏效果如下方视频所示。我们先使用雕塑打开第一道门。来到金色雕像房间,但是不能直接把雕像拿走。我们需要在另一分岔角落,拿上罐子。然后把罐子放在石墩上,就可以把金色雕像带走了。
如果觉得抓取效果不太满意,可以调整 Physics Handle 组件的参数,比如线性阻尼、角阻尼、内插速度等。
我这边觉得之前抓取移动时太抖了,调慢了内插速度,让变动更新有所“延迟”,移动就会变得比较平滑。