這一篇,我們實現敵人被擊敗后,掉落戰利品的功能。首先,我們將創建一個新的結構體,用于定義掉落體的內容,方便我們設置掉落物。然后,我們實現敵人死亡時的掉落函數,并在藍圖里實現對應的邏輯,在場景里生成掉落物。最后,讓掉落物動起來,顯得掉落物需要玩家趕緊去拾取的感覺。
添加新的資產結構體
為了實現對敵人掉落戰利品的配置,我們需要創建一個新的類,作為配置掉落物的新的資產類。
命名為戰利品類
在類里,我們首先添加一個結構體,用于設置一種物品的掉落內容和幾率。
USTRUCT(BlueprintType)
struct FLootItem
{GENERATED_BODY()//戰利品在場景中的顯示效果UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="LootTiers|Spawning")TSubclassOf<AActor> LootClass;//戰利品生成幾率UPROPERTY(EditAnywhere, Category="LootTiers|Spawning")float ChanceToSpawn = 0.f;//物品生成的最大數量UPROPERTY(EditAnywhere, Category="LootTiers|Spawning")int32 MaxNumberToSpawn = 0.f;//修改物品生成等級,false則使用敵人等級UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="LootTiers|Spawning")bool bLootLevelOverride = true;
};
然后在類里,我們增加一個數組,開發者可以配置多個掉落物,然后添加一個函數,用于獲取當前可以生成的戰利品數組。
UCLASS()
class RPG_API ULootTiers : public UDataAsset
{GENERATED_BODY()public://獲取需要生成的戰利品數據UFUNCTION(BlueprintCallable)TArray<FLootItem> GetLootItems();UPROPERTY(EditDefaultsOnly, Category="LootTiers|Spawning")TArray<FLootItem> LootItems;
};
在函數實現這里,我們創建了一個新的數組,用于根據概率計算每個物品的是否可掉落,物品可以設置多個,所以,我們需要兩層嵌套循環,如果當前可掉落,那么我們將其添加到返回數組里。最后返回。
TArray<FLootItem> ULootTiers::GetLootItems()
{TArray<FLootItem> ReturnItems;for(const FLootItem Item : LootItems){for(int32 i=0; i<Item.MaxNumberToSpawn; ++i){if(FMath::RandRange(1.f, 100.f) < Item.ChanceToSpawn){FLootItem NewItem;NewItem.LootClass = Item.LootClass;NewItem.bLootLevelOverride = Item.bLootLevelOverride;ReturnItems.Add(NewItem);}}}return ReturnItems;
}
實現配置和觸發掉落邏輯
然后,我們需要一個能夠去獲取配置好的數據資產的地方,最方便的就是放到GameMode里,我們在GameMode類里添加一個配置,可以在藍圖配置使用哪一個數據資產
//戰利品數據配置UPROPERTY(EditDefaultsOnly, Category="Loot Tiers")TObjectPtr<ULootTiers> LootTiers;
為了方便獲取數據資產,我們在藍圖函數庫里增加一個函數,用于獲取數據資產
/*** 獲取生成的戰利品數據資產,此數據會配置到GameMode上* @param WorldContextObject 一個世界場景的對象,用于獲取當前所在的世界* @return 戰利品數據** @note 敵人死亡后,所需生成的戰利品*/UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|CharacterClassDefaults", meta=(DefaultToSelf = "WorldContextObject"))static ULootTiers* GetLootTiers(const UObject* WorldContextObject);
函數實現,我們將獲取到GameMode,并從GameMode身上獲取到數據資產
ULootTiers* URPGAbilitySystemLibrary::GetLootTiers(const UObject* WorldContextObject)
{//獲取到當前關卡的GameMode實例const ARPGGameMode* GameMode = Cast<ARPGGameMode>(UGameplayStatics::GetGameMode(WorldContextObject));if(GameMode == nullptr) return nullptr;//返回敵人戰利品配置,需要設置到GameMode上return GameMode->LootTiers;
}
最后,我們要在敵人類里實現掉落邏輯,我們增加一個函數,這個函數需要在藍圖里實現邏輯
//生成戰利品UFUNCTION(BlueprintImplementableEvent)void SpawnLoot();
然后在觸發死亡邏輯時,我們調用此函數來生成戰利品
添加藍圖
代碼方便,我們完成了,接著,我們要在編輯器里,添加一個新的數據資產
資產類型使用我們創建的戰利品資產
我們將其和之前的資產放到一塊
然后,在資產里,我們將之前制作的藥瓶和水晶(持續回血)添加到戰利品里,掉落概率設置為100,并且要使用敵人等級掉落。
接著將其設置到GameMode藍圖里
我們在敵人藍圖基類里實現統一的掉落戰利品機制,首先通過藍圖庫函數獲取到數據資產實例,然后通過GetLootItems獲取到需要生成的掉落物
然后創建一個藍圖函數,用于通過藍圖函數庫,來設置掉落物的轉向。
然后我們將其設置為純函數,這樣,不需要通過執行箭頭調用。
然后將轉向存儲起來方便后續使用
然后,我們通過一個定時器,讓物品掉落持續生成,這樣可以防止卡頓,并且物品有一個個生成的效果,我們在設置定時器時,直接調用一次此事件。
接著就是生成戰利品的自定義事件,我們通過索引,從返回的數組里獲取到需要生成的對應的數據,并計算出物品掉落位置,最后生成Actor
我們創建獲取變換的純函數,通過轉向對敵人位置進行一個朝向在一定范圍內隨機偏移,并使用旋轉的朝向。
在創建Actor后,我們需要更新掉落物的等級,如果此物品需要修改等級,那么我們將通過此函數內的邏輯進行修改
函數內,我們首先判斷是否需要修改,然后將其轉換為場景物品的基類,然后判斷當前物品是否存在,最后將敵人等級應用到掉落物身上。
最后一步,就是修改索引,每調用一次,我們將索引+1,下次再調用此事件時,將會生成下一個坐標的物品,如果索引超過或等于數組長度時,我們將結束定時器,完成掉落物的生成。
最后展示一下完成的藍圖連線
然后進入關卡打怪測試效果
關于掉落物的一些擴展
如果后續擴展的話,我考慮對于每個大關卡創建單獨的一套掉落,然后在數組資產里增加幾個數組,比如效果,使用小怪的一套掉落,而精英怪使用精英怪的一套掉落,最后是boss的掉落,使用boss的一套掉落邏輯。
然后獲取掉落時,可以根據敵人品質,去獲取不同的掉落,我們當然沒必須單獨為敵人去配置掉落。BOSS除外,我們當然可以對一些特殊BOSS去配置單獨的掉落,比如關卡的最終BOSS掉落,以及一些特殊怪物掉落。
實現掉落物自動旋轉和懸浮效果
為了實現這個效果,我們要在掉落物的基類RPGEffectActor里增加一些屬性和函數,用于實現此效果
要實現這個功能,我們增加一批函數,用于實現此功能
// 計算后的Actor所在的位置UPROPERTY(BlueprintReadWrite)FVector CalculatedLocation;// 計算后的Actor的旋轉UPROPERTY(BlueprintReadWrite)FRotator CalculatedRotation;// Actor是否幀更新旋轉UPROPERTY(BlueprintReadWrite, Category="Pickup Movement")bool bRotates = false;// Actor每秒旋轉的角度UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Pickup Movement")float RotationRate = 45.f;// Actor是否更新位置UPROPERTY(BlueprintReadWrite, Category="Pickup Movement")bool bSinusoidalMovement = false;// 正弦值-1到1,此值為調整更新移動范圍UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Pickup Movement")float SineAmplitude = 1.f;// 此值參與正弦運算,默認值為1秒一個循環(2PI走完一個正弦的循環,乘以時間,就是一秒一個循環,可用于調整位置移動速度)UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Pickup Movement")float SinePeriod = 1.f; //2 * PI//調用此函數,Actor開始自動更新上下位置UFUNCTION(BlueprintCallable)void StartSinusoidalMovement();//調用此函數,Actor開始自動旋轉UFUNCTION(BlueprintCallable)void StartRotation();
private://當前掉落物的存在時間,可以通過此時間實現動態效果float RunningTime = 0.f;// Actor生成的默認初始位置,在Actor動態浮動時,需要默認位置作為基礎位置FVector InitialLocation;// 每一幀更新Actor的位置和轉向void ItemMovement(float DeltaSeconds);
我們還需要用到幀更新函數
virtual void Tick(float DeltaSeconds) override;
在事件開始時,我們將掉落物的默認位置和旋轉保存,并設置計算后的屬性,我們需要在后續使用它更新Actor
void ARPGEffectActor::BeginPlay()
{Super::BeginPlay();//設置初始位置InitialLocation = GetActorLocation();CalculatedLocation = InitialLocation;CalculatedRotation = GetActorRotation();
}
我們需要在幀更新了去保存當前效果的執行時間,并調用更新函數
void ARPGEffectActor::Tick(float DeltaSeconds)
{Super::Tick(DeltaSeconds);//更新當前Actor的存在時間RunningTime += DeltaSeconds;ItemMovement(DeltaSeconds);
}
在更新函數里,我們根據變量判斷是否需要更新,來對轉向和位置更新,這里需要提到的是,位置更新用到了正弦三角函數進行更新
void ARPGEffectActor::ItemMovement(float DeltaSeconds)
{//更新轉向if(bRotates){const FRotator DeltaRotation(0.f, DeltaSeconds * RotationRate, 0.f);CalculatedRotation = UKismetMathLibrary::ComposeRotators(CalculatedRotation, DeltaRotation);}//更新位置if(bSinusoidalMovement){const float Sine = SineAmplitude * FMath::Sin(RunningTime * SinePeriod * 6.28318f);CalculatedLocation = InitialLocation + FVector(0.f, 0.f, Sine);}
}
你會發現上面的變量無法在藍圖面板直接設置,那么如何將其設置為true呢,我們通用函數將其設置為true
void ARPGEffectActor::StartSinusoidalMovement()
{bSinusoidalMovement = true;InitialLocation = GetActorLocation();CalculatedLocation = InitialLocation;
}void ARPGEffectActor::StartRotation()
{bRotates = true;CalculatedRotation = GetActorRotation();
}
后續效果我們需要在藍圖里實現,所以,我們創建一個拾取的基類,然后將所有可掉落物都繼承此藍圖,沒必要在每個藍圖里實現一遍
我們在拾取物基類里,創建一個時間軸,來實現掉落物的從無到有的效果,并通過時間軸的更新實現一些位置更新,和縮放效果。在時間軸播放完成后,我們調用開始旋轉和開始移動的默認效果。
時間軸里,我們增加了兩個軌道,用于分別更新位置和縮放使用
在幀更新里,我們通過計算后的位置和旋轉更新Actor即可
以下是拾取物的表現效果
添加音效
最后一個功能,我們在Actor里添加一些音效,來實現一些點綴效果。
首先在Actor增加一個變量,設置音效基礎類型
然后找到對應的音效
設置給變量
在更新位置和縮放后(我折疊成了一個函數),我們通過修改位置的值,判斷如果大于0.3時,執行一次模擬觸碰到地面的音效
然后在事件開始時增加一個生成的音效
在銷毀時,播放一個拾取的音效。