-
Notifications
You must be signed in to change notification settings - Fork 0
Step 7: Evolotion System Guide
The Evolution System allows weapons to transform into more powerful versions when they reach max level and the player owns the required passive item. This is a core feature inspired by Vampire Survivors.
✅ Automatic Evolution - Weapons can auto-evolve when conditions are met
✅ Manual Evolution - Players can trigger evolution via chest or button
✅ Flexible Control - Blueprint events for full customization
✅ Passive Item Tracking - Simple array-based passive management
✅ Blueprint-Friendly - All functions exposed to Blueprints
✅ Event System - Multiple events for UI and effects
A weapon can evolve when ALL of these conditions are met:
- ✅ Weapon is at max level
- ✅ Weapon has evolution config (
bCanEvolve = true) - ✅ Player owns the required passive item
Weapon reaches max level
↓
OnWeaponMaxLevel event fires
↓
Check if evolution is possible
↓
OnEvolutionAvailable event fires
↓
[If bAutoEvolveOnMaxLevel = true]
↓
Wait AutoEvolveDelay seconds
↓
Auto-evolve weapon
↓
OnWeaponEvolved event fires
[If bAutoEvolveOnMaxLevel = false]
↓
Wait for manual trigger (chest/button)
↓
Call EvolveWeapon()
↓
OnWeaponEvolved event fires
In your weapon data asset Blueprint (e.g., DA_MagicWand):
Evolution Config:
✓ bCanEvolve = true
✓ RequiredPassiveItem = BP_EmptyTome (your passive ACTOR class, e.g., BP_PassiveEmptyTome)
✓ EvolvedWeapon = DA_HolyWand (your evolved weapon data asset)
✓ EvolutionDescription = "Magic Wand + Empty Tome = Holy Wand"
Important: RequiredPassiveItem should be an Actor class (e.g., BP_PassiveEmptyTome), not a component. Passive items are spawned as actors in the game world.
Option A: Starting Passive Items (Auto-added on game start)
In your Player Blueprint's Weapon Component:
Starting Passive Items:
[0] BP_PassiveEmptyTome
[1] BP_PassiveSpinach
Owned Passive Items: (Read-Only - Auto-synced from Starting Passive Items)
[0] BP_PassiveEmptyTome ← Automatically added!
[1] BP_PassiveSpinach ← Automatically added!
How it works:
- Editor: Add passive to "Starting Passive Items" → Automatically appears in "Owned Passive Items"
- Game Start: Starting weapons equipped (level 1)
- Evolution check runs automatically for all weapons
- If weapon is max level + has passive → Evolution triggers!
Option B: Runtime Passive Items (Acquired during gameplay)
When player acquires a passive item:
// Blueprint:
WeaponComponent->AddPassiveItem(BP_EmptyTome);
// C++:
WeaponComponent->AddPassiveItem(TSoftClassPtr<AActor>(BP_EmptyTome::StaticClass()));How it works:
- Player picks up passive item
-
AddPassiveItem()called - Added to "Owned Passive Items"
- Evolution check runs automatically for all weapons
- If any weapon is max level + has this passive → Evolution triggers!
Important:
- Do NOT add passive items to "Starting Weapons" array - that's only for weapons!
- "Owned Passive Items" is read-only in Editor - it auto-syncs from "Starting Passive Items"
- Use
AddPassiveItem()for runtime additions
// In Player Blueprint BeginPlay:
WeaponComponent->bAutoEvolveOnMaxLevel = true;
WeaponComponent->AutoEvolveDelay = 1.0f; // 1 second delay for UIResult: Weapon auto-evolves when max level + passive conditions are met.
// In Player Blueprint BeginPlay:
WeaponComponent->bAutoEvolveOnMaxLevel = false;
// In Chest Blueprint:
Event OnChestOpened()
{
TArray<int32> EvolvableWeapons = WeaponComponent->GetEvolvableWeapons();
if (EvolvableWeapons.Num() > 0)
{
// Show UI for player to select
ShowEvolutionSelection(EvolvableWeapons);
// When player selects:
WeaponComponent->EvolveWeapon(SelectedSlotIndex);
}
else
{
// No evolutions available, give gold instead
GiveGold(100);
}
}Result: Player manually triggers evolution via chest or button.
Fired when a weapon CAN evolve (max level + has passive).
Event OnEvolutionAvailable(SlotIndex, WeaponData)
{
// Show UI notification
ShowNotification("Evolution Ready: " + WeaponData->WeaponName);
// Play sound
PlaySound(EvolutionReadySound);
}Fired when a weapon successfully evolves.
Event OnWeaponEvolved(SlotIndex, OldWeapon, NewWeapon)
{
// Show evolution animation
PlayEvolutionAnimation(OldWeapon, NewWeapon);
// Show UI popup
ShowEvolutionPopup(NewWeapon->WeaponName);
// Play sound/effect
PlaySound(EvolutionCompleteSound);
SpawnEffect(EvolutionEffect);
}// Add passive item (actor class)
TSoftClassPtr<AActor> PassiveClass = ...;
WeaponComponent->AddPassiveItem(PassiveClass);
// Remove passive item
WeaponComponent->RemovePassiveItem(PassiveClass);
// Check if player has passive
bool HasPassive = WeaponComponent->HasPassiveItem(PassiveClass);
// Get all owned passives
TArray<TSoftClassPtr<AActor>> Passives = WeaponComponent->GetOwnedPassiveItems();// Check if specific weapon can evolve
bool CanEvolve = WeaponComponent->CanWeaponEvolve(SlotIndex);
// Get all weapons that can evolve
TArray<int32> EvolvableSlots = WeaponComponent->GetEvolvableWeapons();
// Get evolution config for a weapon
FURFWeaponEvolutionConfig Config = WeaponComponent->GetWeaponEvolutionConfig(SlotIndex);// Manually trigger evolution
bool Success = WeaponComponent->EvolveWeapon(SlotIndex);When a weapon evolves, the old weapon is automatically banned to prevent it from appearing in level-up cards:
// Check if a weapon is banned
bool IsBanned = WeaponComponent->IsWeaponBanned(WeaponData);
// Get all banned weapons
TArray<UURFWeaponDataAsset*> Banned = WeaponComponent->BannedWeapons;
// Example: Filter level-up card pool
TArray<UURFWeaponDataAsset*> GetAvailableWeapons()
{
TArray<UURFWeaponDataAsset*> Available;
for (UURFWeaponDataAsset* Weapon : AllWeapons)
{
// Skip banned weapons (evolved weapons)
if (WeaponComponent->IsWeaponBanned(Weapon))
{
continue;
}
// Skip owned weapons
if (WeaponComponent->HasWeapon(Weapon))
{
continue;
}
Available.Add(Weapon);
}
return Available;
}How it works:
- Weapon evolves (Magic Wand → Holy Wand)
- Old weapon (Magic Wand) added to
BannedWeapons - Old weapon's persistent actors destroyed
- Level-up cards filter out banned weapons
- Magic Wand can't be picked again this run
Weapon Name: Magic Wand
Max Level: 8
Base Damage: 15
Evolution Config:
bCanEvolve: true
RequiredPassiveItem: BP_PassiveEmptyTome (Actor class)
EvolvedWeapon: DA_HolyWand
EvolutionDescription: "Fires more projectiles with holy power"
Weapon Name: Holy Wand
Max Level: 8
Base Damage: 30
Base Amount: 3 // Fires 3 projectiles instead of 1
Evolution Config:
bCanEvolve: false // Cannot evolve further
// Simple actor component
UCLASS()
class UEmptyTomeComponent : public UActorComponent
{
GENERATED_BODY()
// No logic needed, just exists as a marker
};// BeginPlay:
WeaponComponent->bAutoEvolveOnMaxLevel = false; // Manual evolution
// When player picks up Empty Tome:
Event OnPickupEmptyTome()
{
WeaponComponent->AddPassiveItem(BP_EmptyTome);
ShowNotification("Acquired: Empty Tome");
}
// Evolution Available Event:
Event OnEvolutionAvailable(SlotIndex, WeaponData)
{
ShowNotification("Evolution Ready: " + WeaponData->WeaponName);
PlaySound(EvolutionReadySound);
}
// Evolution Complete Event:
Event OnWeaponEvolved(SlotIndex, OldWeapon, NewWeapon)
{
ShowEvolutionAnimation(OldWeapon, NewWeapon);
PlaySound(EvolutionCompleteSound);
}Event OnChestOpened()
{
// Check for evolvable weapons
TArray<int32> EvolvableWeapons = WeaponComponent->GetEvolvableWeapons();
if (EvolvableWeapons.Num() > 0)
{
// Evolve first available weapon
int32 SlotToEvolve = EvolvableWeapons[0];
WeaponComponent->EvolveWeapon(SlotToEvolve);
}
else
{
// No evolutions, give gold
CurrencyComponent->AddCurrency("Gold", 100);
}
}// Player Blueprint:
WeaponComponent->bAutoEvolveOnMaxLevel = true;
WeaponComponent->AutoEvolveDelay = 2.0f;
Event OnEvolutionAvailable(SlotIndex, WeaponData)
{
// Show 2-second notification before auto-evolve
ShowNotification("Evolving: " + WeaponData->WeaponName, 2.0f);
}// Player Blueprint:
WeaponComponent->bAutoEvolveOnMaxLevel = false;
// Chest Blueprint:
Event OnChestOpened()
{
TArray<int32> EvolvableWeapons = WeaponComponent->GetEvolvableWeapons();
if (EvolvableWeapons.Num() > 0)
{
// Show selection UI if multiple evolutions
if (EvolvableWeapons.Num() > 1)
{
ShowEvolutionSelectionUI(EvolvableWeapons);
}
else
{
// Auto-evolve single weapon
WeaponComponent->EvolveWeapon(EvolvableWeapons[0]);
}
}
}// Player Blueprint:
WeaponComponent->bAutoEvolveOnMaxLevel = false;
Event OnEvolutionAvailable(SlotIndex, WeaponData)
{
// Show "Press E to Evolve" UI
ShowEvolutionPrompt(SlotIndex, WeaponData);
}
Event OnInputAction_Evolve()
{
TArray<int32> EvolvableWeapons = WeaponComponent->GetEvolvableWeapons();
if (EvolvableWeapons.Num() > 0)
{
WeaponComponent->EvolveWeapon(EvolvableWeapons[0]);
}
}| Property | Type | Default | Description |
|---|---|---|---|
bAutoEvolveOnMaxLevel |
bool | false | Auto-evolve when conditions met |
AutoEvolveDelay |
float | 0.5 | Delay before auto-evolution (seconds) |
OwnedPassiveItems |
TArray | Empty | List of owned passive actor classes |
| Property | Type | Description |
|---|---|---|
bCanEvolve |
bool | Whether weapon can evolve |
RequiredPassiveItem |
TSoftClassPtr | Required passive actor class |
EvolvedWeapon |
TSoftObjectPtr | Evolved weapon data asset |
EvolutionDescription |
FString | Description for UI |
- Set
bCanEvolve = falseon evolved weapons (prevent double evolution) - Use descriptive evolution descriptions for UI
- Test evolution with both auto and manual modes
- Provide visual/audio feedback on evolution
- Show "Evolution Ready" notifications to player
- Create evolution loops (A → B → A)
- Forget to set RequiredPassiveItem (must be an Actor class, not component)
- Forget to create the evolved weapon data asset
- Make evolved weapons too weak (should feel powerful!)
- Add passive items to "Starting Weapons" array (use "Starting Passive Items" instead!)
Check:
- ✅ Weapon is at max level? (
CurrentLevel >= MaxLevel) - ✅
bCanEvolve = truein weapon data asset? - ✅
RequiredPassiveItemis set to an Actor class (not component)? - ✅
EvolvedWeaponis set? - ✅ Player owns the passive item? (
HasPassiveItem()) - ✅
bAutoEvolveOnMaxLevelsetting matches your intent? - ✅ Passive item added via
AddPassiveItem()or "Starting Passive Items"? (NOT "Starting Weapons"!)
❌ WRONG:
Starting Weapons:
[0] DA_MagicWand
[1] BP_PassiveEmptyTome ← This is wrong!
✅ CORRECT:
Starting Weapons:
[0] DA_MagicWand
Starting Passive Items:
[0] BP_PassiveEmptyTome ← Use this array!
Check:
- ✅ Event is implemented in Blueprint?
- ✅ Delegate is bound in Actor Blueprint?
- ✅ Enable debug logging:
bEnableDebugLogging = true
Check:
- ✅ Passive class matches exactly in data asset and code?
- ✅
AddPassiveItem()was called? - ✅ Check owned passives:
GetOwnedPassiveItems()
Enable debug logging to see evolution system in action:
// In Player Blueprint:
WeaponComponent->bEnableDebugLogging = true;Log Output:
URFWeaponComponent: Added passive item: BP_EmptyTome (Total: 1)
URFWeaponComponent: Weapon 'Magic Wand' can now evolve!
URFWeaponComponent: Auto-evolving weapon in 0.50 seconds...
URFWeaponComponent: Weapon evolved! Magic Wand -> Holy Wand (Slot 0)
-
v1.0 - Initial evolution system implementation
- Automatic and manual evolution modes
- Passive item tracking
- Blueprint events and delegates
- Full Vampire Survivors-style support
- Discord: https://discord.gg/QJxvpuyUv8
- GitHub: https://github.com/CanTATARDev/UltimateRoguelikeFramework
Document Version: 1.0.0
Last Updated: 2026-02-07
Status: ✅ Production Ready