关卡润色

这篇文章是这个解密游戏 demo 的最后一篇。在之前的文章中,所有的基本功能均已实现。在这篇文章中,我们只是增加一些关卡内容,让游戏更加丰富。

如图 1 所示,我们在关卡中添加了新内容。我们添加了一个石墩(SM_Statue_Stand)和一个金色的雕像(SM_Statue_Hooded_Metal)。这个金色的雕像就是我们最终想要带走的物体。我们还添加了一扇门(SM_Cell_Side)。

图1 新内容

金色雕塑需要是可以抓取的对象,所以我们将其 碰撞预设 设置为 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,回到蓝图界面上右击,会自动显示创建该对象引用的选项。

图2 蓝图

不要忘记把金色雕像和门的 移动性 属性设置为 可移动

我们想要石墩上有物体重叠的话,触发门向上打开;石墩上没有物体的话,门会回到原来关闭的状态。

如代码清单 1 所示,我们为了方便,选择尽量复用之前的代码。我们新增 ActiveTargetLocation 变量指示真正的终点:有物体重叠的时候,ActiveTargetLocation 是之前逻辑的 TargetLocation;没有物体重叠的时候,ActiveTargetLocation 设置为 InitialLocation,即回到初始位置。

同时为了防止 Mover 的 Tick 频繁调用,在到达目的地后,就关闭 Tick。

像 ShouldMove 这些原来的变量名,在新功能场景下,其实含义都发现变化了。为了方便不做修改了。

代码清单 1 Mover
  1. // Called when the game starts
  2. void UMover::BeginPlay()
  3. {
  4.     Super::BeginPlay();
  5.  
  6.     if (AActor* Owner = GetOwner())
  7.     {
  8.         InitialLocation = Owner->GetActorLocation();
  9.         TargetLocation = InitialLocation + TargetOffset;
  10.         CalculateMoveSpeed();
  11.  
  12.         ActiveTargetLocation = InitialLocation;
  13.     }
  14. }
  15.  
  16.  
  17. // Called every frame
  18. void UMover::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
  19. {
  20.     Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
  21.  
  22.     if (AActor* Owner = GetOwner())
  23.     {
  24.         FVector CurrentLocation = Owner->GetActorLocation();
  25.         FVector NewLocation = FMath::VInterpConstantTo(
  26.             CurrentLocation, ActiveTargetLocation, DeltaTime, MoveSpeed
  27.         );
  28.         Owner->SetActorLocation(NewLocation);
  29.  
  30.         // Stop moving
  31.         if (FVector::Dist(NewLocation, ActiveTargetLocation) < 1.0f)
  32.         {
  33.             SetComponentTickEnabled(false);
  34.             UE_LOG(LogTemp, Log, TEXT("UMover: %s reached target. Tick disabled."), *Owner->GetName());
  35.         }
  36.     }
  37. }
  38.  
  39. void UMover::SetShouldMove(bool NewShouldMove)
  40. {
  41.     ShouldMove = NewShouldMove;
  42.     if (ShouldMove)
  43.         ActiveTargetLocation = TargetLocation;
  44.     else
  45.         ActiveTargetLocation = InitialLocation;
  46.  
  47.     SetComponentTickEnabled(true);
  48. }

相应的,Trigger 的代码也做了一些修改。如代码清单 2 所示,我们新增 bWasAcceptingActor 变量,用来记录当前是否有物体和组件重叠。只有物体重叠状态发生了变化,才触发 Mover 移动,防止 Mover 被频繁调用。

代码清单 2 Trigger
  1. // Called every frame
  2. void UTriggerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
  3. {
  4.     Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
  5.  
  6.     AActor* Actor = GetAcceptableActor();
  7.     bool bHasActorNow = (Actor != nullptr);
  8.     if (bHasActorNow != bWasAcceptingActor)
  9.     {
  10.         if (bHasActorNow)
  11.         {
  12.             UE_LOG(LogTemp, Display, TEXT("Unlocking"));
  13.             UPrimitiveComponent* Component = Cast<UPrimitiveComponent>(Actor->GetRootComponent());
  14.             if (Mover != NULL && Component != NULL)
  15.             {
  16.                 Component->SetSimulatePhysics(false);
  17.                 Actor->AttachToComponent(this, FAttachmentTransformRules::KeepWorldTransform);
  18.                 Mover->SetShouldMove(true);
  19.             }
  20.         }
  21.         else
  22.         {
  23.             UE_LOG(LogTemp, Display, TEXT("Relocking"));
  24.             if (Mover != NULL)
  25.                 Mover->SetShouldMove(false);
  26.         }
  27.     }
  28.  
  29.     bWasAcceptingActor = bHasActorNow;
  30. }

现在的逻辑是,把金色雕像从石墩上拿走,门就会关闭,导致无法出去。应对的策略是,我们需要在石墩上放一个代替的物体。

我们在关卡的另一个角落里,添加一个罐子 SM_Pot_A_Body。把它当作金色雕像的替代,设置和金色雕像一样:碰撞预设 设置为 Custom,并使 Grabber 通道的碰撞响应方式为 阻挡;添加“Unlock2”标签;勾选 生成重叠事件移动性 属性设置为 可移动

最终的游戏效果如下方视频所示。我们先使用雕塑打开第一道门。来到金色雕像房间,但是不能直接把雕像拿走。我们需要在另一分岔角落,拿上罐子。然后把罐子放在石墩上,就可以把金色雕像带走了。

如果觉得抓取效果不太满意,可以调整 Physics Handle 组件的参数,比如线性阻尼、角阻尼、内插速度等。

我这边觉得之前抓取移动时太抖了,调慢了内插速度,让变动更新有所“延迟”,移动就会变得比较平滑。