Loading
Personal Project
Charming Craft

CharmingCraft is a top-down action-adventure game built from scratch in Unreal Engine 5 with C++, inspired by Minecraft: Dungeons and Albion Online. I designed and implemented over 10 interconnected gameplay systems, including a custom Gameplay Ability System (GAS) that powers both player actions and item skill extensions, a data-driven buff framework, a Chain of Responsibility damage pipeline, behavior tree AI with Environment Query System (EQS), modular building and farming, a DataAsset-based crafting system, dynamic level streaming with cross-scene item persistence, and a save slot manager built on a double-linked list. The project showcases my ability to architect complex, loosely coupled game systems in C++ while keeping them fully extensible through Blueprint for rapid content iteration. GitHub Repository

Loford Town with houses and NPCs
The town of Loford, featuring player-built houses and roaming NPCs in the open world

Combat and Ability Framework

Item Skill System (Albion Online-Inspired)

One of the most distinctive features of CharmingCraft is its item skill system, directly inspired by Albion Online. Every item in the game can carry multiple skills across 9 distinct skill slots, and players can choose which skills to bind from each item’s skill pool. This creates deep build diversity where the same sword can behave completely differently depending on the player’s skill selection.

Item skill reference from Albion Online
The Albion Online item skill system that inspired CharmingCraft’s design, where each equipment piece offers multiple selectable skills

I implemented this through UItemDynamicSkill, a skill pool container attached to each item, with EItemDynamicSkillSlot defining 9 slot types: USE, SHIFT, SHIFT_INTERACT, RIGHT_CLICK, HOTBAR_CAST, INTERACT, PASSIVE, ON_HIT, and ON_ATTACK. Each slot serves a different gameplay purpose. PASSIVE slots provide continuous buffs like armor bonuses, ON_HIT slots trigger effects on weapon contact such as frost or poison, and ON_ATTACK slots fire when the player initiates an attack, enabling effects like chain lightning from a cape.

The binding flow works as follows: each item defines its available skills in ItemDynamicSkill.DynamicSkills. When a player opens the skill selection UI, they see all available skills for each slot. The selected skill is stored in ItemMeta.BindItemDynamicSkill as a TMap mapping slot types to UNativeAction instances. The UCraftActionComponent then manages these bound actions at runtime, triggering them based on the corresponding input or event.

Item skill selection UI
The in-game item skill UI showing available skills per slot, allowing players to customize their loadout
0:00
/0:00
Weapon skill selection demo showing the player browsing and binding different skills to equipment slots

Action and Combat System

The combat system is built on a custom Gameplay Ability System (GAS) using Unreal’s GameplayTags infrastructure. I designed a layered action hierarchy where UNativeAction serves as the base class, UNativeItemAction extends it with item stack requirements, UNativeEquipmentAction adds equipment entity actor support, and specialized classes like UNativeStandardMeleeAction and UNativeStandardRangedAction handle specific combat styles.

graph TD
    subgraph Action Hierarchy
        NA[UNativeAction]
        NIA[UNativeItemAction]
        NEA[UNativeEquipmentAction]
        NSMA[UNativeStandardMeleeAction]
        NSRA[UNativeStandardRangedAction]
        NOHA[UNativeOnHitAction]
    end

    NA --> NIA
    NIA --> NEA
    NIA --> NOHA
    NEA --> NSMA
    NEA --> NSRA

    subgraph Action Lifecycle
        CS[CanStart] --> SA[StartAction]
        SA --> IR[IsRunning]
        IR --> STA[StopAction]
        STA --> CD[StartCoolDown]
        CD --> CF[CooldownFinished]
    end

The melee action system uses continuous line tracing per frame through StartMeleeActionTrace(), checking for hits via MeleeActionTick() and applying damage through OnActionHit(). Ranged actions spawn ABaseActionActor projectiles with their own lifecycle hooks: OnActionActorSpawn(), OnActionActorTick(), OnActionActorHit(), and OnActionActorDestroy(). These action actors support chained behaviors through OnHitActions, OnSpawnActions, and OnTickActions, enabling complex ability compositions like a projectile that spawns area-of-effect flames on impact.

The damage pipeline follows the Chain of Responsibility pattern. Each FHitData carries physical damage, magic damage, true damage, critical hit data, and an OnHitBuffList for applying buffs on contact. The UDamageChain processes damage through a sequence of handlers: PhysicalDamageHandler applies armor reduction, MagicDamageHandler applies magic defense, TrueDamageHandler passes damage unmitigated, and BuffApplicationHandler applies any on-hit buffs. This pattern makes it trivial to add new damage types or processing steps without modifying existing code.

I also implemented a 5-part modular weapon assembly system through ASwordEntityActorP5, which renders Blade, Fuller, Guard, Hilt, and Pommel as separate mesh components. Each part can use different materials and meshes, allowing runtime customization of weapon appearance through UMaterialInstanceDynamic without creating new asset classes.

Buff and Attribute System

The buff system is a self-contained, data-driven framework where designers define buff behavior entirely through UBuffData templates and UBaseBuffModel Blueprint subclasses. UBuffInfo instances track runtime state including remaining duration, current stack count, and references to the instigator and target. The UBuffHandlerComponent manages the active buff list, ticking durations each frame and removing expired buffs.

Buff system data-driven configuration
Data-driven buff configuration in the editor, showing how designers author buff properties, stacking policies, and lifecycle models without C++

Stacking and time policies provide fine-grained control: EBuffTimeUpdate determines whether reapplying a buff adds duration, replaces it, or keeps the existing timer. EBuffRemoveStackUpdate controls whether removal clears all stacks or decrements one at a time. The UModifyPropertyBuffModel subclass directly modifies FPlayerAttribute deltas, enabling buffs that boost attack damage, reduce movement speed, or apply damage-over-time effects.

The attribute system centers on UDAttributeComponent, which tracks Health, Mana, Damage, Ability Power, Armour, Magic Defense, Knockback Resistance, Critical Chance, Critical Damage, Attack Speed, Movement Speed, Level, and XP. This component is shared across players and creatures, ensuring consistent combat mechanics throughout the game.

AI and Creature Behavior

I implemented two distinct AI archetypes to demonstrate behavior tree and EQS capabilities. The skeleton enemy uses EQS to evaluate its environment, maintaining distance from the player while executing ranged attacks and self-healing when health drops below a threshold. The Crimson Furbeak is a neutral creature that roams peacefully until attacked, at which point it chases the aggressor. When the attacker moves out of range, the Furbeak returns to its roaming state.

0:00
/0:00
Skeleton enemy using EQS to maintain distance from the player, executing ranged attacks and self-healing behavior
0:00
/0:00
Crimson Furbeak neutral creature demonstrating roaming, aggro on hit, chase behavior, and return to roaming when losing aggro

All creatures inherit from ANativeCreature, which implements IDamageable, IMouseInteractInterface, IGameplayTagAssetInterface, and ILootableInterface. The ANativeCreatureAIController automatically runs the creature’s assigned behavior tree on possession. Each creature supports configurable hit response montages mapped by EDamageResponse, spawn point tracking with return-to-spawn logic, and a drop table system for loot generation.

0:00
/0:00
Entity drop items demo showing creatures dropping loot on death with animated item pickups featuring rotation and bobbing effects

The drop system uses UDropTableData assets where each entry specifies an item material, min/max quantity, and drop probability. Dropped items spawn as ADropItem actors with rotation and vertical bobbing animations, and players pick them up through the interaction system.

Sandbox Systems

Building System

The building system uses a Strategy pattern through UBaseBuildModel to decouple tool-specific placement logic from the core UBuildModuleManager. The manager handles ray tracing from camera to mouse position, grid snapping at 100-unit intervals, collision validation through FPlaceValidation, and visual feedback via AFrameActor preview frames that change color based on placement validity.

When a player equips a building tool, the corresponding UBaseBuildModel subclass activates. UToolBuildModel handles tools like the hoe and pickaxe, while USeedsBuildModel manages seed placement. Each model implements ActivateBuildModel(), StartTrace(), OnPlace(), and DeactivateBuildModel() for its specific behavior. Buildings placed in the world persist across scene transitions, and structures like houses can contain interior sub-levels loaded through TSoftObjectPtr<UWorld>.

0:00
/0:00
Building system demo showing block placement with grid snapping, visual validation, and persistence across scene transitions

Farming System

The farming system reuses the building infrastructure to minimize code duplication. Equipping a Hoe activates the HoeBuildModel, which shows a farmable grid overlay. Clicking valid ground creates a Farm_Land block. Players then equip seeds, which activate the USeedsBuildModel for planting. The system deducts seeds from inventory on placement and manages crop growth through ABlockEntityActor tick logic.

0:00
/0:00
Farming system demo showing the full workflow of tilling land with a hoe, planting seeds, and watering crops

Crafting System

The crafting system is built on UBaseRecipeEntry DataAssets, where each recipe defines its name, namespace, owning container, category, ingredient list, and output items. The URecipeRegistry serves as a central database, loading all recipes at startup into a TMap<FName, FRecipesContainerCollection> organized by container type.

Crafting system UI
The crafting panel UI showing recipe categories, ingredient requirements, and output preview

The UBaseCraftHandler validates ingredients through the OnCheckRecipeIngredientMatch event, then broadcasts OnCraftProcessStart and OnCraftProcessFinish during execution. This event-driven approach allows the UI to display progress animations and sound effects without coupling to the craft logic.

0:00
/0:00
Full crafting system demo showing recipe browsing, ingredient validation, and item creation

Resource Gathering

Resource gathering integrates with Unreal Engine’s Chaos destruction system for visually satisfying breakable resources. When players mine gems or harvest materials, the resource entity fractures using physics simulation before dropping items into the world.

Resource gathering with Chaos system
Player gathering gems with Unreal Engine’s Chaos destruction system, showing physics-based fracturing on resource break

World and Persistence

World Management and Scene Transition

The world system supports three scene types: Regions (persistent open world), Scenes (instanced interiors like houses), and Dungeons (instanced challenge areas). The UWorldManager orchestrates multiple UNativeCraftWorld instances, each wrapping a ULevelStreamingDynamic for efficient memory management through TSoftObjectPtr<UWorld> lazy loading.

Map selection UI
Map selection interface showing available regions with chunk-based world preview
0:00
/0:00
Map selection UI with animated chunk transitions and per-map descriptions for each region

Players travel between worlds through ACraftWarpPoint actors, with the manager handling world loading, visibility toggling, and player list tracking. Only one world is visible at a time, and the system automatically hides others during transitions. Items dropped in the world persist across scene changes, maintaining gameplay continuity.

0:00
/0:00
Item persistence demo showing dropped items surviving across different scene transitions

The chunk system through ALandChunk manages world streaming at a granular level, with each chunk tracking its own EChunkState (LOADED, PENDING_UNLOADED, UNLOADED), biome data, resource entity pools, and creature spawn pools.

Save System and Player Creation

The save system centers on UGameSaveManager, which uses a double-linked list (TDoubleLinkedList<FSaveSlotInfo>) for circular navigation through save slots. Each FSaveSlotInfo contains a unique GUID, slot path, display name, and four data containers: UPlayerData for character state and inventory, URealmData for world state, UProgressData for quest tracking, and ULevelData for level-specific information.

The double-linked list structure enables smooth UI navigation where players can scroll endlessly through their save slots in either direction. The system supports slot creation with GenerateDefaultData(), deletion with a bPrepareDelete safety flag, and preview slots for the selection UI. All game objects implement the ISerializable interface with SerializeToJson() and DeserializeFromJson() methods, enabling any UObject to participate in the save pipeline without modifying core systems.

0:00
/0:00
Main menu save system showing player save selection with circular navigation and new character creation

System Architecture Overview

All systems are orchestrated through UCharmingCraftInstance, the central game instance that owns every manager: UGameSaveManager, UWorldManager, UBuildModuleManager, UCameraManager, URecipeRegistry, UNativeDungeonHandler, and UPlayerModeManager. The UGameEventHandler acts as a global event bus with 40+ multicast delegates, enabling loose coupling between all systems.

graph TD
    subgraph Game Instance
        GI[UCharmingCraftInstance]
    end

    subgraph Core Managers
        SM[UGameSaveManager]
        WM[UWorldManager]
        BM[UBuildModuleManager]
        CM[UCameraManager]
        RR[URecipeRegistry]
        DH[UNativeDungeonHandler]
        PM[UPlayerModeManager]
    end

    subgraph Event Bus
        GEH[UGameEventHandler]
    end

    subgraph Entity Systems
        PC[ANativePlayerCharacter]
        NC[ANativeCreature]
        DI[ADropItem]
    end

    subgraph Shared Components
        AC[UCraftActionComponent]
        IC[UInventoryComponent]
        EC[UEquipmentComponent]
        DA[UDAttributeComponent]
        BC[UBuffHandlerComponent]
    end

    GI --> SM
    GI --> WM
    GI --> BM
    GI --> CM
    GI --> RR
    GI --> DH
    GI --> PM
    GI --> GEH

    GEH -.->|broadcasts| PC
    GEH -.->|broadcasts| NC
    GEH -.->|broadcasts| BM
    GEH -.->|broadcasts| WM

    PC --> AC
    PC --> IC
    PC --> EC
    PC --> DA
    PC --> BC

    NC --> AC
    NC --> IC
    NC --> DA
    NC --> BC

Design Philosophy

Data-Driven Content — Every major system uses DataAssets or Blueprint-subclassable base classes for content definition. Buff behaviors, crafting recipes, drop tables, item skills, and creature configurations are all authored without C++ compilation, enabling rapid iteration.

Event-Driven Decoupling — The UGameEventHandler centralizes 40+ multicast delegates across world management, inventory, building, crafting, combat, and movement categories. Systems communicate through events rather than direct references, eliminating circular dependencies and enabling modular development.

Strategy Pattern for Extensibility — The building system’s UBaseBuildModel, the damage pipeline’s UDamageHandler chain, and the buff system’s UBaseBuffModel all follow the Strategy pattern. New tools, damage types, and buff effects plug in as subclasses without touching existing code, adhering to the Open-Closed Principle.

Component-Based Entity Architecture — Players and creatures share the same component set (UDAttributeComponent, UCraftActionComponent, UBuffHandlerComponent, UInventoryComponent), ensuring consistent combat mechanics across all entities. This composition-over-inheritance approach makes it straightforward to add new entity types with any combination of capabilities.

Interface-Driven Serialization — The ISerializable interface with SerializeToJson() and DeserializeFromJson() allows any UObject to participate in the save pipeline. This decouples persistence logic from game logic, so new saveable objects require zero changes to the save manager.