Kila: Hourbound is a third-person action-adventure platformer built in Unreal Engine 5.6 for PC and Steam Deck. Set in the world of Aion, the game features melee combat, platforming, and a time-manipulation mechanic for puzzle solving and combat abilities. For this team project (C34 Teal Group), I built the gameplay systems and UI architecture, spanning four custom C++ modules and a global event subsystem.
Gameplay trailer
Core contributions
I implemented four C++ modules and one global subsystem to support design and animation workflows in Blueprint:
- GameCommonModule: Shared foundation defining attribute structs, damage payload contracts, ability haste interfaces, and scaling HUD base widgets.
- BuffModule: Data-driven status effect system supporting custom stacking policies, duration timers, and reactive UI containers.
- CoreAttributeModule: Actor attribute component with modifier pipelines, damage resolution, and health bar widgets for characters and bosses.
- ActionModule: Gameplay Ability System built on GameplayTags with action charges, cooldown reductions, and hierarchical ability entities.
- EventSubsystem: GameInstance subsystem providing dynamic multicast delegates for decoupled communication across systems.
Module architecture
The four modules form a layered dependency structure. GameCommonModule provides shared data types, BuffModule and CoreAttributeModule build on that foundation, and ActionModule integrates all three. The EventSubsystem routes cross-system notifications.
graph TD
subgraph GameCommonModule["GameCommonModule (Foundation)"]
AD[FAttributeData]
DR[FDamageRequest / FDamageResult]
AHI[IAbilityHasteListener]
HWB[UHudWidgetBase]
WTT[UWidgetToolTip]
GC[UGameCommon Utils]
end
subgraph BuffModule["BuffModule (Buff/Debuff)"]
BD[UBuffDefinition]
BI[UBuffInstance]
BM[UBuffModel]
BC[UBuffComponent]
BWC[UWidgetBuffContainer]
end
subgraph CoreAttributeModule["CoreAttributeModule (RPG Attributes)"]
CAC[UCoreAttributeComponent]
DAM[IDamageable]
PHB[UWidgetPlayerHealthBar]
BHB[UWidgetBossHealthBar]
end
subgraph ActionModule["ActionModule (GAS / Abilities)"]
UA[UAction]
UAC[UActionComponent]
AE[AAbilityEntity]
end
subgraph EventSubsystem["EventSubsystem (Global Event Bus)"]
ES[UEventSubsystem]
end
GameCommonModule --> BuffModule
GameCommonModule --> CoreAttributeModule
BuffModule --> CoreAttributeModule
GameCommonModule --> ActionModule
CoreAttributeModule --> ActionModule
ES -.->|broadcasts| ActionModule
ES -.->|broadcasts| CoreAttributeModule
ES -.->|broadcasts| BuffModule
GameCommonModule foundation layer
This module defines common data structures, interfaces, and base widget classes used by other modules, containing definitions rather than gameplay rules.
Attribute and damage data types
RPG attributes are declared in EAttributeType: Health, MaxHealth, MovementSpeed, Defense, AttackDamage, ResistPhysic, DamageReductionPhysic, ResistMagic, DamageReductionMagical, and AbilityHaste. The struct FAttributeData bundles these values and implements operator overloads (+, -, +=, -=) for clean attribute arithmetic.
Damage calculations use two primary structs:
FDamageRequestpackages BaseDamage, EDamageType (Physical, Magical, TrueDamage, DirectHeal), critical hit chances, multipliers, and block flags.FDamageResultreturns FinalDamage, before and after health values, combat flags, and attribute snapshots for VFX and UI reactions.
Modifiers use FAttributeModifier with four calculation modes: FlatAdd, PercentAdd, PercentMult, and Override, each storing a priority and source pointer for targeted removal.
Ability haste interface
IAbilityHasteListener and FAbilityHasteContext define how components listen to ability haste changes. Cooldown reduction follows the formula CDR = Haste / (Haste + 100). Per-action haste entries (FPerActionHasteModifier) allow buffs to target specific action categories, such as dashes.
HUD widget templates
UHudWidgetBase monitors viewport size every 0.1 seconds and scales child elements against a 1920x1080 baseline. UWidgetToolTip calculates dynamic height, wraps text lines, and stabilizes positioning to prevent edge-of-screen flicker.
BuffModule buff and debuff system
BuffModule provides a data-driven buff framework where behaviors are configured through UBuffDefinition assets and UBuffModel Blueprint subclasses.
Class diagram
classDiagram
class UBuffDefinition {
+int32 Id
+FName BuffName
+UTexture2D* Icon
+int32 MaxStack
+EBuffType BuffType
+bool bIsForever
+float Duration
+float TickTime
+EBuffTimeUpdate BuffTimeUpdate
+EBuffRemoveStackUpdate BuffRemoveStackUpdate
+TSubclassOf~UBuffModel~ OnCreate
+TSubclassOf~UBuffModel~ OnRemove
+TSubclassOf~UBuffModel~ OnTick
}
class UBuffInstance {
+UBuffDefinition* BuffDefinition
+AActor* Instigator
+AActor* Target
+float Duration
+int32 CurrentStack
+GetRemainingTime() float
+GetRemainingPercentage() float
+GetTimeStatus() EBuffTimeStatus
+MarkAsRemoved()
}
class UBuffModel {
+UBuffInstance* ParentBuffInstance
+Apply()
+OnCleanup()
}
class UBuffComponent {
+TArray~UBuffInstance*~ BuffList
+AddBuff(UBuffInstance*)
+RemoveBuff(UBuffInstance*)
+TickBuffAndRemove(float DeltaTime)
+GetBuffsByTimeStatus()
+GetBuffUIDisplayData()
}
class UWidgetBuffContainer {
+TArray~EBuffType~ BuffTypeFilters
+InitializeWithBuffComponent()
+RefreshBuffDisplay()
}
class UWidgetBuffElement {
+UBuffInstance* BuffInstance
+UpdateDisplayData()
}
UBuffDefinition --> UBuffInstance : instantiates
UBuffInstance --> UBuffModel : lifecycle callbacks
UBuffComponent --> UBuffInstance : manages
UWidgetBuffContainer --> UWidgetBuffElement : creates per buff
UWidgetBuffElement --> UBuffInstance : displays
UWidgetBuffContainer --> UBuffComponent : observes
Core architecture
UBuffDefinition specifies buff assets: icon, maximum stacks, base duration, periodic tick intervals, classification (Positive, Negative, Neutral), and model references for OnCreate, OnRemove, and OnTick hooks. UBuffModel exposes BlueprintNativeEvent methods for visual and mechanical scripting.
At runtime, UBuffComponent manages an array of active UBuffInstance objects, tracking remaining durations, stack counts, and instigator references.
Stacking and time policies
Reapplying an existing buff resolves according to two policies:
EBuffTimeUpdate:Addadds incoming duration to the current timer,Replaceresets to maximum duration, andKeepmaintains the active timer.EBuffRemoveStackUpdate:Clearremoves all stacks simultaneously, whileReducedecrements stacks one at a time.
Buff lifecycle flow
flowchart TD
A[AddBuffFromBuffDefinition] --> B{Buff already exists?}
B -->|No| C[Create UBuffInstance]
C --> D[Add to BuffList, sort by Priority]
D --> F[Execute OnCreate BuffModel]
F --> G[Broadcast BuffAddToBuffList]
B -->|Yes| H{Stack < MaxStack?}
H -->|Yes| I[Increment stack, update duration per policy]
I --> K[Broadcast BuffAddToBuffListRepeat]
H -->|No| L[Apply time update only]
L --> K
G --> M[TickComponent each frame]
K --> M
M --> N{TickTime elapsed?}
N -->|Yes| P[Execute OnTick BuffModel, reset timer]
N -->|No| R[Decrement Duration]
P --> R
R --> S{Expired or marked removed?}
S -->|Yes| T[Execute OnRemove BuffModel]
T --> U{RemoveStackUpdate policy}
U -->|Clear| V[Remove entirely, destroy instance]
U -->|Reduce| W{Stack <= 0?}
W -->|Yes| V
W -->|No| Y[Reset Duration, continue]
S -->|No| M
Time tracking API
UBuffInstance exposes remaining time calculations through GetRemainingTime(), GetRemainingPercentage(), and status categories (Permanent, Active, AboutToExpire, Expired). UBuffComponent aggregates display payloads via GetBuffUIDisplayData() for HUD elements.
UI widget hierarchy
The buff HUD uses three layers: UWidgetBuffHudCanvas coordinates multiple UWidgetBuffContainer panels (filtered by positive or negative buff types), which instantiate UWidgetBuffElement widgets for individual buffs. Elements update stack counts and duration text, opening UWidgetBuffTip on hover.
Debug console
UBuffConsoleCommands provides console triggers during development: AddBuff, RemoveBuff, ListBuffs, ClearAllBuffs, and ShowBuffInfo.
CoreAttributeModule attribute system
This module tracks attribute state, resolves incoming damage and healing requests, and coordinates combat HUD elements.
Attribute component
UCoreAttributeComponent attaches to characters and monsters, storing:
AttributeDataBase: baseline editor-configured stats.AttributeModifiers: active modifiers from buffs, equipment, or passives.CachedFinalAttributes: recalculated attribute sums.
Modifiers evaluate in a fixed order: Flat additions first, then additive percentages, multiplicative percentages, and direct overrides.
Damage pipeline
flowchart LR
A[FDamageRequest] --> B[ProcessDamageRequest]
B --> C[Snapshot attributes before]
B --> D{DamageType?}
D -->|DirectHeal| E[ApplyHealing]
D -->|Physical| F[Apply ResistPhysic + DamageReductionPhysic]
D -->|Magical| G[Apply ResistMagic + DamageReductionMagical]
D -->|TrueDamage| H[No reduction]
F --> I{bCanBeCrit?}
G --> I
H --> I
I -->|Yes| J[Roll CritChance]
J -->|Crit| K[Apply CritMultiplier]
J -->|Miss| L[Base damage]
I -->|No| L
K --> M[Apply to Health]
L --> M
M --> P[Snapshot attributes after]
P --> Q{Health <= 0?}
Q -->|Yes| R[KillingBlow = true]
Q -->|No| S[KillingBlow = false]
R --> T[FDamageResult]
S --> T
E --> T
The component broadcasts delegates for combat events: DamageProcessedEvent, HealthChangedEvent, CharacterDeathEvent, and OnAttributeModifierChanged.
Damageable interface
Actors that take damage implement IDamageable, exposing AppendAttributeData() so damage processors can inspect incoming targets uniformly.
Health bar widgets
The module provides two health bar widgets:
UWidgetPlayerHealthBar: Binds to the player attribute component, displaying shield absorption, current health, and status indicators.UWidgetBossHealthBar: Displays boss names, phase titles, portraits, and low-health indicators when reaching critical stages.
Debug tooling
UAttributeDebugSubsystem identifies actors with attribute components in the world, toggling an in-world debug overlay showing live health, resistance modifiers, and active stat alterations.
ActionModule ability system
ActionModule implements an ability system built around Unreal GameplayTags, using UAction for ability definitions, UActionComponent for runtime execution, and AAbilityEntity for spawned effects.
Class diagram
classDiagram
class UAction {
+FName ActionName
+float CoolDown
+int ActionLevel
+int ActionCharge
+FGameplayTagContainer GrantsTags
+FGameplayTagContainer BlockedTags
+FGameplayTagContainer ActionFeatureTags
+StartAction(AActor*)
+StopAction(AActor*)
+CanActionStart(AActor*) bool
+GetFinalCooldown() float
+GetTotalAbilityHaste() float
+AddPerActionHasteModifier(float, UObject*)
}
class UActionComponent {
+TArray~UAction*~ Actions
+FGameplayTagContainer ActiveGamePlayTags
+float CachedAbilityHaste
+AddAction(TSubclassOf~UAction~)
+RemoveAction(TSubclassOf~UAction~)
+StartActionByName(AActor*, FName)
+ReduceCooldownByTag(FGameplayTag, float, bool)
}
class AAbilityEntity {
+FName EntityName
+float MaxLifetime
+FDamageRequest BaseDamageRequest
+float DamageScalingPerLevel
+AAbilityEntity* ParentEntity
+TArray~AAbilityEntity*~ ChildEntities
+InitializeEntity(AActor*, UAction*)
+SpawnChildEntity(TSubclassOf~AAbilityEntity~, FTransform, bool)
+DealDamageToTarget(AActor*)
}
class IGameplayTagAssetInterface {
<<interface>>
+GetOwnedGameplayTags()
}
class IAbilityHasteListener {
<<interface>>
+OnAbilityHasteChanged()
+GetCachedAbilityHaste() float
}
UActionComponent --> UAction : manages
UActionComponent ..|> IGameplayTagAssetInterface
UActionComponent ..|> IAbilityHasteListener
UAction --> AAbilityEntity : spawns
AAbilityEntity --> AAbilityEntity : parent-child hierarchy
Action ability definitions
Key properties on UAction include:
- Gameplay tags:
GrantsTagsapply to the owner during execution,BlockedTagsprevent activation when present, andActionFeatureTagscategorize the skill. - Cooldown and haste: Cooldowns combine global ability haste with per-action bonuses:
FinalCooldown = BaseCooldown * (1 - CDR). - Stored charges: Abilities configure multiple charges before cooldown kicks in.
- Level progression:
ActionLevelsupports skill progression trees; level 0 abilities cannot be cast.
Action component manager
UActionComponent manages abilities on an actor, implementing IGameplayTagAssetInterface for tag queries and IAbilityHasteListener for stat synchronizations. It handles tag-based cooldown reductions, tag-filtered haste adjustments, and ability lifecycle events.
Spawned ability entities
AAbilityEntity represents actors spawned by abilities (projectiles, ground markers, persistent zones). Features include:
- Hierarchical child spawning with depth limits.
- Source tracking linking entities back to their instigator and parent action.
- Damage request templates scaled automatically by ability level.
- Overridable lifecycle and collision hooks (
OnEntitySpawned,OnEntityBeginOverlap,OnEntityHit).
EventSubsystem global event bus
UEventSubsystem centralizes global delegates across the project:
// From any UObject with a world context
EventSystem->OnCountdownTick.AddDynamic(this, &AMyClass::HandleTick);
// Explicit world context version
GetEvent(GetWorld())->CountDownFinished.Broadcast();
Event categories
- Time and world pacing: Delegates manage dilation transitions (
OnGlobalTimeDilationChanged), countdown clocks, and targeted time slow mechanics on specific actors. - Character states:
GlobalCharacterDeathEventalerts encounter logic, and combo delegates track melee animation attack chains. - UI triggers:
RequestWidgetVisibilityChangelets gameplay actors request HUD updates without holding direct widget pointers. - Level flow:
LevelOpenUpsignals when scene transition barriers are clear.
Design decisions
Blueprint extensibility
Methods are exposed as BlueprintNativeEvent or BlueprintCallable, allowing designers to author new abilities, buffs, and damage triggers in Blueprint without recompiling C++ source.
GameplayTag classification
GameplayTags replace hardcoded enums for action gating and batch modifications, letting designers group abilities into families like dashes or spells dynamically.
Modifier source tracking
Attribute modifiers and status effects retain references to their origin actor or component, ensuring clean removal when buffs expire.
Delegate-driven decoupling
Systems exchange data through dynamic multicast delegates. Combat processors broadcast damage results, while health bars and Niagara damage counters listen independently.
Built-in console tooling
Buff and attribute components include console command handlers and world-space debug overlays to inspect stats, modifier stacks, and cooldowns during testing.