美文网首页
十三、RPGPlayerControllerBase.h/cpp

十三、RPGPlayerControllerBase.h/cpp

作者: 珏_Gray | 来源:发表于2019-06-18 15:17 被阅读0次

    PlayController是每个游戏都必须有的。在ActionRPG中,RPGPlayerControllerBase负责背包系统,即背包数据以及交互方法。


    背包:3个物品槽,1个技能槽,1个药水槽

    Data

    背包中的数据有:
    *TMap<URPGItem*, FRPGItemData> InventoryData : Key:物品类型 , Value: 物品在背包中的数据
    *TMap<FRPGItemSlot, URPGItem*> SlottedItems : Key:物品槽, Value:物品类型


    Methods

    除了数据外,剩下的就是一堆和数据交互的方法:add、remove、get、set、save、load,和各种delegate:用来将消息传递到外部系统。直接上代码(a lot of code TT,尽量加注释):

    头文件:

    
    #pragma once
    
    #include "ActionRPG.h"
    #include "GameFramework/PlayerController.h"
    #include "RPGInventoryInterface.h"
    #include "RPGPlayerControllerBase.generated.h"
    
    /** Base class for PlayerController, should be blueprinted */
    UCLASS()
    class ACTIONRPG_API ARPGPlayerControllerBase : public APlayerController, public IRPGInventoryInterface
    {
        GENERATED_BODY()
    
    public:
        // Constructor and overrides
        ARPGPlayerControllerBase() {}
        virtual void BeginPlay() override;
    
    // 数据
    // -------------------------------------------------------------------------------------------------------------
        /** Map of all items owned by this player, from definition to data */
        UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Inventory)
        TMap<URPGItem*, FRPGItemData> InventoryData;
    
        /** Map of slot, from type/num to item, initialized from ItemSlotsPerType on RPGGameInstanceBase */
        UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Inventory)
        TMap<FRPGItemSlot, URPGItem*> SlottedItems;
    // -------------------------------------------------------------------------------------------------------------
    
    // 委托和蓝图事件(用于消息传递)
    // -------------------------------------------------------------------------------------------------------------
        /** Delegate called when an inventory item has been added or removed */
        UPROPERTY(BlueprintAssignable, Category = Inventory)
        FOnInventoryItemChanged OnInventoryItemChanged;
    
        /** Native version above, called before BP delegate */
        FOnInventoryItemChangedNative OnInventoryItemChangedNative;
    
        /** Delegate called when an inventory slot has changed */
        UPROPERTY(BlueprintAssignable, Category = Inventory)
        FOnSlottedItemChanged OnSlottedItemChanged;
    
        /** Called after the inventory was changed and we notified all delegates */
        UFUNCTION(BlueprintImplementableEvent, Category = Inventory)
        void InventoryItemChanged(bool bAdded, URPGItem* Item);
    
        /** Called after an item was equipped and we notified all delegates */
        UFUNCTION(BlueprintImplementableEvent, Category = Inventory)
        void SlottedItemChanged(FRPGItemSlot ItemSlot, URPGItem* Item);
    
        /** Native version above, called before BP delegate */
        FOnSlottedItemChangedNative OnSlottedItemChangedNative;
    
        /** Delegate called when the inventory has been loaded/reloaded */
        UPROPERTY(BlueprintAssignable, Category = Inventory)
        FOnInventoryLoaded OnInventoryLoaded;
    
        /** Native version above, called before BP delegate */
        FOnInventoryLoadedNative OnInventoryLoadedNative;
    // -------------------------------------------------------------------------------------------------------------
    
    //背包操作
    // -------------------------------------------------------------------------------------------------------------
        /** Adds a new inventory item, will add it to an empty slot if possible. If the item supports count you can add more than one count. It will also update the level when adding if required */
        UFUNCTION(BlueprintCallable, Category = Inventory)
        bool AddInventoryItem(URPGItem* NewItem, int32 ItemCount = 1, int32 ItemLevel = 1, bool bAutoSlot = true);
    
        /** Remove an inventory item, will also remove from slots. A remove count of <= 0 means to remove all copies */
        UFUNCTION(BlueprintCallable, Category = Inventory)
        bool RemoveInventoryItem(URPGItem* RemovedItem, int32 RemoveCount = 1);
    
        /** Returns all inventory items of a given type. If none is passed as type it will return all */
        UFUNCTION(BlueprintCallable, Category = Inventory)
        void GetInventoryItems(TArray<URPGItem*>& Items, FPrimaryAssetType ItemType);
    
        /** Returns number of instances of this item found in the inventory. This uses count from GetItemData */
        UFUNCTION(BlueprintPure, Category = Inventory)
        int32 GetInventoryItemCount(URPGItem* Item) const;
    
        /** Returns the item data associated with an item. Returns false if none found */
        UFUNCTION(BlueprintPure, Category = Inventory)
        bool GetInventoryItemData(URPGItem* Item, FRPGItemData& ItemData) const;
    
        /** Sets slot to item, will remove from other slots if necessary. If passing null this will empty the slot */
        UFUNCTION(BlueprintCallable, Category = Inventory)
        bool SetSlottedItem(FRPGItemSlot ItemSlot, URPGItem* Item);
    
        /** Returns item in slot, or null if empty */
        UFUNCTION(BlueprintPure, Category = Inventory)
        URPGItem* GetSlottedItem(FRPGItemSlot ItemSlot) const;
    
        /** Returns all slotted items of a given type. If none is passed as type it will return all */
        UFUNCTION(BlueprintCallable, Category = Inventory)
        void GetSlottedItems(TArray<URPGItem*>& Items, FPrimaryAssetType ItemType, bool bOutputEmptyIndexes);
    
        /** Fills in any empty slots with items in inventory */
        UFUNCTION(BlueprintCallable, Category = Inventory)
        void FillEmptySlots();
    // -------------------------------------------------------------------------------------------------------------
    
    //保存和加载背包数据
    // -------------------------------------------------------------------------------------------------------------
        /** Manually save the inventory, this is called from add/remove functions automatically */
        UFUNCTION(BlueprintCallable, Category = Inventory)
        bool SaveInventory();
    
        /** Loads inventory from save game on game instance, this will replace arrays */
        UFUNCTION(BlueprintCallable, Category = Inventory)
        bool LoadInventory();
    // -------------------------------------------------------------------------------------------------------------
    
    
    // 接口实现
    // -------------------------------------------------------------------------------------------------------------
        // Implement IRPGInventoryInterface
        virtual const TMap<URPGItem*, FRPGItemData>& GetInventoryDataMap() const override
        {
            return InventoryData;
        }
        virtual const TMap<FRPGItemSlot, URPGItem*>& GetSlottedItemMap() const override
        {
            return SlottedItems;
        }
        virtual FOnInventoryItemChangedNative& GetInventoryItemChangedDelegate() override
        {
            return OnInventoryItemChangedNative;
        }
        virtual FOnSlottedItemChangedNative& GetSlottedItemChangedDelegate() override
        {
            return OnSlottedItemChangedNative;
        }
        virtual FOnInventoryLoadedNative& GetInventoryLoadedDelegate() override
        {
            return OnInventoryLoadedNative;
        }
    // -------------------------------------------------------------------------------------------------------------
    
    
    // 内部方法
    // -------------------------------------------------------------------------------------------------------------
    protected:
        /** Auto slots a specific item, returns true if anything changed */
        bool FillEmptySlotWithItem(URPGItem* NewItem);
    
        /** Calls the inventory update callbacks */
        void NotifyInventoryItemChanged(bool bAdded, URPGItem* Item);
        void NotifySlottedItemChanged(FRPGItemSlot ItemSlot, URPGItem* Item);
        void NotifyInventoryLoaded();
    // -------------------------------------------------------------------------------------------------------------
    };
    
    

    源文件:

    // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
    
    #include "RPGPlayerControllerBase.h"
    #include "RPGCharacterBase.h"
    #include "RPGGameInstanceBase.h"
    #include "RPGSaveGame.h"
    #include "Items/RPGItem.h"
    
    /** 往背包里添加物品,
    1) 如果是全新的物品,且auto slot为true,则添加到空的道具槽中
    2) 如果道具支持叠加,则该道具持有数+1
    3) 会更新道具等级
    4) 如果添加道具成功,则保存背包数据
    */
    bool ARPGPlayerControllerBase::AddInventoryItem(URPGItem* NewItem, int32 ItemCount, int32 ItemLevel, bool bAutoSlot)
    {
        bool bChanged = false;
        if (!NewItem)
        {
            UE_LOG(LogActionRPG, Warning, TEXT("AddInventoryItem: Failed trying to add null item!"));
            return false;
        }
    
        if (ItemCount <= 0 || ItemLevel <= 0)
        {
            UE_LOG(LogActionRPG, Warning, TEXT("AddInventoryItem: Failed trying to add item %s with negative count or level!"), *NewItem->GetName());
            return false;
        }
    
        // Find current item data, which may be empty
        FRPGItemData OldData;
        GetInventoryItemData(NewItem, OldData);
    
        // Find modified data
        FRPGItemData NewData = OldData;
        NewData.UpdateItemData(FRPGItemData(ItemCount, ItemLevel), NewItem->MaxCount, NewItem->MaxLevel);
    
        if (OldData != NewData)
        {
            // If data changed, need to update storage and call callback
            InventoryData.Add(NewItem, NewData);
            NotifyInventoryItemChanged(true, NewItem);
            bChanged = true;
        }
    
        if (bAutoSlot)
        {
            // Slot item if required
            bChanged |= FillEmptySlotWithItem(NewItem);
        }
    
        if (bChanged)
        {
            // If anything changed, write to save game
            SaveInventory();
            return true;
        }
        return false;
    }
    /** 移除道具
    */
    bool ARPGPlayerControllerBase::RemoveInventoryItem(URPGItem* RemovedItem, int32 RemoveCount)
    {
        if (!RemovedItem)
        {
            UE_LOG(LogActionRPG, Warning, TEXT("RemoveInventoryItem: Failed trying to remove null item!"));
            return false;
        }
    
        // Find current item data, which may be empty
        FRPGItemData NewData;
        GetInventoryItemData(RemovedItem, NewData);
    
        if (!NewData.IsValid())
        {
            // Wasn't found
            return false;
        }
    
        // If RemoveCount <= 0, delete all
          // 计算移除后的数量
        if (RemoveCount <= 0)
        {
            NewData.ItemCount = 0;
        }
        else
        {
            NewData.ItemCount -= RemoveCount;
        }
    
        if (NewData.ItemCount > 0)
        {
            // Update data with new count
    //注意: TMap的Key是唯一的,因此,此处的add实际上是替换掉过去的Value
            InventoryData.Add(RemovedItem, NewData);
        }
        else
        {
            // Remove item entirely, make sure it is unslotted
            InventoryData.Remove(RemovedItem);
    
            for (TPair<FRPGItemSlot, URPGItem*>& Pair : SlottedItems)
            {
                if (Pair.Value == RemovedItem)
                {
                    Pair.Value = nullptr;
                    NotifySlottedItemChanged(Pair.Key, Pair.Value);
                }
            }
        }
    
        // If we got this far, there is a change so notify and save
        NotifyInventoryItemChanged(false, RemovedItem);
    
        SaveInventory();
        return true;
    }
    /** 返回背包中所有给定type类型的item*/
    void ARPGPlayerControllerBase::GetInventoryItems(TArray<URPGItem*>& Items, FPrimaryAssetType ItemType)
    {
        for (const TPair<URPGItem*, FRPGItemData>& Pair : InventoryData)
        {
            if (Pair.Key)
            {
                FPrimaryAssetId AssetId = Pair.Key->GetPrimaryAssetId();
    
                // Filters based on item type
                if (AssetId.PrimaryAssetType == ItemType || !ItemType.IsValid())
                {
                    Items.Add(Pair.Key);
                }
            }   
        }
    }
    
    bool ARPGPlayerControllerBase::SetSlottedItem(FRPGItemSlot ItemSlot, URPGItem* Item)
    {
        // Iterate entire inventory because we need to remove from old slot
        bool bFound = false;
        for (TPair<FRPGItemSlot, URPGItem*>& Pair : SlottedItems)
        {
    //将道具添加到指定slot
            if (Pair.Key == ItemSlot)
            {
                // Add to new slot
                bFound = true;
                Pair.Value = Item;
                NotifySlottedItemChanged(Pair.Key, Pair.Value);
            }
    //如果再别slot发现了该道具,则从slot中移除
            else if (Item != nullptr && Pair.Value == Item)
            {
                // If this item was found in another slot, remove it
                Pair.Value = nullptr;
                NotifySlottedItemChanged(Pair.Key, Pair.Value);
            }
        }
    //如果找到slot,保存改变后的背包数据
        if (bFound)
        {
            SaveInventory();
            return true;
        }
    
        return false;
    }
    /** 获得道具的数量*/
    int32 ARPGPlayerControllerBase::GetInventoryItemCount(URPGItem* Item) const
    {
        const FRPGItemData* FoundItem = InventoryData.Find(Item);
    
        if (FoundItem)
        {
            return FoundItem->ItemCount;
        }
        return 0;
    }
    /** 获得道具的背包数据*/
    bool ARPGPlayerControllerBase::GetInventoryItemData(URPGItem* Item, FRPGItemData& ItemData) const
    {
        const FRPGItemData* FoundItem = InventoryData.Find(Item);
    
        if (FoundItem)
        {
            ItemData = *FoundItem;
            return true;
        }
        ItemData = FRPGItemData(0, 0);
        return false;
    }
    /** 获得道具槽内的道具*/
    URPGItem* ARPGPlayerControllerBase::GetSlottedItem(FRPGItemSlot ItemSlot) const
    {
        URPGItem* const* FoundItem = SlottedItems.Find(ItemSlot);
    
        if (FoundItem)
        {
            return *FoundItem;
        }
        return nullptr;
    }
    /** 返回给定type的所有slot中的道具*/
    void ARPGPlayerControllerBase::GetSlottedItems(TArray<URPGItem*>& Items, FPrimaryAssetType ItemType, bool bOutputEmptyIndexes)
    {
        for (TPair<FRPGItemSlot, URPGItem*>& Pair : SlottedItems)
        {
            if (Pair.Key.ItemType == ItemType || !ItemType.IsValid())
            {
                Items.Add(Pair.Value);
            }
        }
    }
    /** 自动填满空的slot*/
    void ARPGPlayerControllerBase::FillEmptySlots()
    {
        bool bShouldSave = false;
        for (const TPair<URPGItem*, FRPGItemData>& Pair : InventoryData)
        {
            bShouldSave |= FillEmptySlotWithItem(Pair.Key);
        }
    
        if (bShouldSave)
        {
            SaveInventory();
        }
    }
    /** 保存背包数据*/
    bool ARPGPlayerControllerBase::SaveInventory()
    {
        UWorld* World = GetWorld();
        URPGGameInstanceBase* GameInstance = World ? World->GetGameInstance<URPGGameInstanceBase>() : nullptr;
    
        if (!GameInstance)
        {
            return false;
        }
    
        URPGSaveGame* CurrentSaveGame = GameInstance->GetCurrentSaveGame();
        if (CurrentSaveGame)
        {
            // Reset cached data in save game before writing to it
            CurrentSaveGame->InventoryData.Reset();
            CurrentSaveGame->SlottedItems.Reset();
    
            for (const TPair<URPGItem*, FRPGItemData>& ItemPair : InventoryData)
            {
                FPrimaryAssetId AssetId;
    
                if (ItemPair.Key)
                {
                    AssetId = ItemPair.Key->GetPrimaryAssetId();
                    CurrentSaveGame->InventoryData.Add(AssetId, ItemPair.Value);
                }
            }
    
            for (const TPair<FRPGItemSlot, URPGItem*>& SlotPair : SlottedItems)
            {
                FPrimaryAssetId AssetId;
    
                if (SlotPair.Value)
                {
                    AssetId = SlotPair.Value->GetPrimaryAssetId();
                }
                CurrentSaveGame->SlottedItems.Add(SlotPair.Key, AssetId);
            }
    
            // Now that cache is updated, write to disk
            GameInstance->WriteSaveGame();
            return true;
        }
        return false;
    }
    /** 加载背包数据*/
    bool ARPGPlayerControllerBase::LoadInventory()
    {
        InventoryData.Reset();
        SlottedItems.Reset();
    
        // Fill in slots from game instance
        UWorld* World = GetWorld();
        URPGGameInstanceBase* GameInstance = World ? World->GetGameInstance<URPGGameInstanceBase>() : nullptr;
    
        if (!GameInstance)
        {
            return false;
        }
    
        for (const TPair<FPrimaryAssetType, int32>& Pair : GameInstance->ItemSlotsPerType)
        {
            for (int32 SlotNumber = 0; SlotNumber < Pair.Value; SlotNumber++)
            {
                SlottedItems.Add(FRPGItemSlot(Pair.Key, SlotNumber), nullptr);
            }
        }
    
        URPGSaveGame* CurrentSaveGame = GameInstance->GetCurrentSaveGame();
        URPGAssetManager& AssetManager = URPGAssetManager::Get();
        if (CurrentSaveGame)
        {
            // Copy from save game into controller data
            bool bFoundAnySlots = false;
            for (const TPair<FPrimaryAssetId, FRPGItemData>& ItemPair : CurrentSaveGame->InventoryData)
            {
                URPGItem* LoadedItem = AssetManager.ForceLoadItem(ItemPair.Key);
    
                if (LoadedItem != nullptr)
                {
                    InventoryData.Add(LoadedItem, ItemPair.Value);
                }
            }
    
            for (const TPair<FRPGItemSlot, FPrimaryAssetId>& SlotPair : CurrentSaveGame->SlottedItems)
            {
                if (SlotPair.Value.IsValid())
                {
                    URPGItem* LoadedItem = AssetManager.ForceLoadItem(SlotPair.Value);
                    if (GameInstance->IsValidItemSlot(SlotPair.Key) && LoadedItem)
                    {
                        SlottedItems.Add(SlotPair.Key, LoadedItem);
                        bFoundAnySlots = true;
                    }
                }
            }
    
            if (!bFoundAnySlots)
            {
                // Auto slot items as no slots were saved
                FillEmptySlots();
            }
    
            NotifyInventoryLoaded();
    
            return true;
        }
    
        // Load failed but we reset inventory, so need to notify UI
        NotifyInventoryLoaded();
    
        return false;
    }
    
    //内部函数
    //------------------------------------------------------------------------------------------------------------
    bool ARPGPlayerControllerBase::FillEmptySlotWithItem(URPGItem* NewItem)
    {
        // Look for an empty item slot to fill with this item
        FPrimaryAssetType NewItemType = NewItem->GetPrimaryAssetId().PrimaryAssetType;
        FRPGItemSlot EmptySlot;
        for (TPair<FRPGItemSlot, URPGItem*>& Pair : SlottedItems)
        {
            if (Pair.Key.ItemType == NewItemType)
            {
                if (Pair.Value == NewItem)
                {
                    // Item is already slotted
                    return false;
                }
                else if (Pair.Value == nullptr && (!EmptySlot.IsValid() || EmptySlot.SlotNumber > Pair.Key.SlotNumber))
                {
                    // We found an empty slot worth filling
                    EmptySlot = Pair.Key;
                }
            }
        }
    
        if (EmptySlot.IsValid())
        {
            SlottedItems[EmptySlot] = NewItem;
            NotifySlottedItemChanged(EmptySlot, NewItem);
            return true;
        }
    
        return false;
    }
    
    void ARPGPlayerControllerBase::NotifyInventoryItemChanged(bool bAdded, URPGItem* Item)
    {
        // Notify native before blueprint
        OnInventoryItemChangedNative.Broadcast(bAdded, Item);
        OnInventoryItemChanged.Broadcast(bAdded, Item);
    
        // Call BP update event
        InventoryItemChanged(bAdded, Item);
    }
    
    void ARPGPlayerControllerBase::NotifySlottedItemChanged(FRPGItemSlot ItemSlot, URPGItem* Item)
    {
        // Notify native before blueprint
        OnSlottedItemChangedNative.Broadcast(ItemSlot, Item);
        OnSlottedItemChanged.Broadcast(ItemSlot, Item);
    
        // Call BP update event
        SlottedItemChanged(ItemSlot, Item);
    }
    
    void ARPGPlayerControllerBase::NotifyInventoryLoaded()
    {
        // Notify native before blueprint
        OnInventoryLoadedNative.Broadcast();
        OnInventoryLoaded.Broadcast();
    }
    //----------------------------------------------------------------------------------------------------------------------
    void ARPGPlayerControllerBase::BeginPlay()
    {
        // Load inventory off save game before starting play
        LoadInventory();
    
        Super::BeginPlay();
    }
    

    相关文章

      网友评论

          本文标题:十三、RPGPlayerControllerBase.h/cpp

          本文链接:https://www.haomeiwen.com/subject/umefqctx.html