UI 中为同一演员创建的 UE4 C++ 多个小部件

UE4 C++ Multiple widgets created for the same actor in UI

我正在尝试建立一个系统来显示玩家附近的地面战利品(即与玩家的 UBoxComponent 重叠的 actors)。这需要随着玩家移动和地面战利品演员不再重叠而改变。

我有点让它起作用了——当演员们独自一人时,这个功能似乎起作用了。然而,当两个地面战利品演员彼此靠近时,清单会为地板上的同一个重叠演员显示多个小部件,有时只能识别地板上的一个演员。当 Player 的 UBoxComponent 一次与多个 actor 重叠时,它似乎遇到了麻烦。我将 link 一个 YouTube 视频来帮助展示我遇到的问题,因为它很难描述:https://youtu.be/a_zMl1zOUDc

这是更新地面战利品部件的功能。它由从播放器 class:

广播的动态多播委托调用
// Iterates through the ground loot around the player and displays a widget for each item
void UInventory::UpdateGroundLoot()
    {
        MyPlayer = Cast<AMainCharacter>(GetOwningPlayer()->GetCharacter());
        
        if(!MyPlayer) { return; } // Null check
        
        // Get a ref to the Player's inventory component
        UInventoryComponent* PlayerInventoryComp = MyPlayer->PlayerInventory;
        
        // Clear any current widgets in the ground loot list
        GroundLootScrollBox->ClearChildren();
        
        // GroundLootActorsArray is an array of AActors that gets added to/emptied based on AActors that overlap with the Player's UBoxComponent
        if(!PlayerInventoryComp && !PlayerInventoryComp->GroundLootActorsArray.IsValidIndex(0)) { return; } // Null check
        
        for(int32 i = 0; i < PlayerInventoryComp->GroundLootActorsArray.Num(); i++)
        {
            // Cast each element of the GroundLootItems array to AItem* GroundItem
            AItem* GroundItem = Cast<AItem>(PlayerInventoryComp->GroundLootActorsArray[i]);
            
            if(!GroundItem) { return; } // Null check
            
            // Add GroundItem to a new array (GroundLootItemsArray). This is an array specifically of AItems (rather than AActors).
            GroundLootItemsArray.Add(GroundItem);
            
            GroundLootWidget = CreateWidget<UInventoryItemWidget>(GetOwningPlayer(), InventoryItemWidgetClass);
            
            // Add each created widget to an array of widgets called GroundLootWidgetsArray
            GroundLootWidgetsArray.Add(GroundLootWidget);
            
            if(!GroundLootWidgetsArray.IsValidIndex(0) && !GroundLootItemsArray.IsValidIndex(0)) { return; } // Null check
            
            // Display item specific names/thumbnails for each widget created
            GroundLootWidgetsArray[i]->ItemName->SetText(GroundLootItemsArray[i]->ItemDisplayName);
            GroundLootWidgetsArray[i]->Thumbnail->SetBrushFromTexture(GroundLootItemsArray[i]->Thumbnail);
            
            // Add each array element of the GroundLootWidgetsArray to the scroll box in the widget blueprint
            GroundLootScrollBox->AddChild(GroundLootWidgetsArray[i]);       
        }   
    }

评论中的 DevilsD 在这里为我指明了正确的方向。我遇到了两个主要问题。

  1. 旨在更新地面战利品小部件的委托正在 for loop 中针对 OverlappingActor 数组的每个元素进行广播。我认为这导致为同一个演员显示多个小部件。 Player 的 class 中的代码现在看起来像这样(TriggerEnterTriggerExit 分别由构造函数中的 OnComponentBeginOverlapOnComponentEndOverlap 委托调用):
    void AMainCharacter::TriggerEnter(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
        UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
    {
        bItemIsWithinRange = true;
        
        // I think the TSubclassOf ensures that we only pickup AItem classes and it's children - rather than all actors on the ground
        OverlappedComponent->GetOverlappingActors(OverlappingActors, TSubclassOf<AItem>(AItem::StaticClass()));
    
        if(PlayerInventory && OverlappingActors.IsValidIndex(0))
        {
            for(auto& GroundItems : OverlappingActors)
            {
                PlayerInventory->AddItemToGroundLoot(GroundItems);
            }
            OnUpdateGroundLoot.Broadcast();
        }
    }

    void AMainCharacter::TriggerExit(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
        UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
    {
        bItemIsWithinRange = false;
    
        if(PlayerInventory && OverlappingActors.IsValidIndex(0))
        {
            for(auto& GroundItems : OverlappingActors)
            {
                PlayerInventory->GroundLootActorsArray.Remove(GroundItems);
            }
            OnUpdateGroundLoot.Broadcast();
            OverlappingActors.Empty();
        }
    }
  1. 我并没有在每次更新地面战利品时清空 GroundLootItemsArray,因此每次新更新时,以前的地面战利品都会存储在这个数组中。这也导致为两个不同的演员显示相同的小部件。在 UInventory.cpp(UUserWidget 的子项)中进行任何小部件创建之前,我现在将其设置为空并清除滚动框的子项:
// Iterates through the ground loot around the player and displays a widget for each item
void UInventory::UpdateGroundLoot()
{
    MyPlayer = Cast<AMainCharacter>(GetOwningPlayer()->GetCharacter());
    
    if(!MyPlayer) { return; } // Null check
    
    // Get a ref to the Player's inventory component
    UInventoryComponent* PlayerInventoryComp = MyPlayer->PlayerInventory;
    
    // Clear any current widgets in the ground loot list
    GroundLootScrollBox->ClearChildren();
    // Empties this array to prevent replication of Item specific information that gets sent to the widget (i.e., the thumbnail or the name of the item)
    GroundLootItemsArray.Empty();
    
    // GroundLootActorsArray is an array of AActors that gets added to/emptied based on AActors that overlap with the Player's UBoxComponent
    if(!PlayerInventoryComp && !PlayerInventoryComp->GroundLootActorsArray.IsValidIndex(0)) { return; } // Null check
    
    for(int32 i = 0; i < PlayerInventoryComp->GroundLootActorsArray.Num(); i++)
    {
        // Cast each element of the GroundLootItems array to AItem* GroundItem
        AItem* GroundItem = Cast<AItem>(PlayerInventoryComp->GroundLootActorsArray[i]);
        
        if(!GroundItem) { return; } // Null check
        
        // Add GroundItem to a new array (GroundLootItemsArray). This is an array specifically of AItems (rather than AActors).
        GroundLootItemsArray.AddUnique(GroundItem);
        
        GroundLootWidget = CreateWidget<UInventoryItemWidget>(GetOwningPlayer(), InventoryItemWidgetClass);
        
        // Add each created widget to an array of widgets called GroundLootWidgetsArray
        GroundLootWidgetsArray.AddUnique(GroundLootWidget);
        
        if(!GroundLootWidgetsArray.IsValidIndex(0) && !GroundLootItemsArray.IsValidIndex(0)) { return; } // Null check
        
        // Display item specific names/thumbnails for each widget created
        GroundLootWidgetsArray[i]->ItemName->SetText(GroundLootItemsArray[i]->ItemDisplayName);
        GroundLootWidgetsArray[i]->Thumbnail->SetBrushFromTexture(GroundLootItemsArray[i]->Thumbnail);
        
        // Add each array element of the GroundLootWidgetsArray to the scroll box in the widget blueprint
        GroundLootScrollBox->AddChild(GroundLootWidgetsArray[i]);       
    }   
}

我还将 actors/items 添加到数组的方式从 Array.Add(Actor) 更改为 Array.AddUnique(Actor)。我不确定这是否有必要,但这对我来说很有意义,因为我不希望任何给定项目中的一个以上被意外添加到数组中。