Gameplay Pillars
- >Combat readability over raw visual noise
- >Consistent input response under pressure
- >Enemy behaviors that escalate tension without feeling unfair
Gameplay Programmer
I owned gameplay programming for combat feel, AI encounter flow, and player feedback loops. The goal was to make every fight legible, tense, and responsive while still running comfortably on mid-tier hardware.
Duration
4 months
Team
3 developers
Engine
Unreal Engine 5
Platforms
PC
Spawns and retargets enemy archetypes based on player health, ammo, and current arena zone.
Impact: Reduced repetitive waves and improved average session retention during internal playtests.
Synchronized VFX, animation notifies, and damage windows in a single data-driven timing table.
Impact: Made incoming attacks easier to read, cutting avoidable damage spikes for new players.
Added short command buffering for dodge and counter actions during recovery frames.
Impact: Greatly improved responsiveness perception without trivializing combat difficulty.
Screenshots and clips from gameplay, debug tools, and iteration passes.

Final combat arena lighting and readability pass.

Runtime debug overlay used to tune lock-on target scoring.
Short clip showing enemy telegraphs and counter timing windows.
Focused clips showing implementation outcomes, tuning passes, and polished gameplay moments.
Before/after comparison of hit-stop tuning, camera impulse, and recovery buffering.
Practical samples from gameplay systems and runtime tools used in production.
CombatTargetingComponent.cpp | cpp
Weighted scoring that combines player facing and distance to choose a lock-on target.
AActor* UCombatTargetingComponent::ChooseTarget(
const TArray<AActor*>& Candidates,
const FVector& ForwardVector,
const FVector& OwnerPosition
) const
{
float BestScore = -FLT_MAX;
AActor* BestTarget = nullptr;
for (AActor* Candidate : Candidates)
{
if (!IsValid(Candidate))
{
continue;
}
const FVector ToTarget = (Candidate->GetActorLocation() - OwnerPosition).GetSafeNormal();
const float FacingScore = FVector::DotProduct(ForwardVector, ToTarget);
const float DistanceScore = 1.0f - FMath::Clamp(
FVector::Distance(OwnerPosition, Candidate->GetActorLocation()) / MaxLockDistance,
0.0f,
1.0f
);
const float FinalScore = (FacingScore * 0.7f) + (DistanceScore * 0.3f);
if (FinalScore > BestScore)
{
BestScore = FinalScore;
BestTarget = Candidate;
}
}
return BestTarget;
}PlayerCombatState.cpp | cpp
Buffers dodge input while attack animation recovers to keep controls responsive.
void UPlayerCombatState::HandleBufferedInputs(float DeltaSeconds)
{
BufferedDodgeTime -= DeltaSeconds;
if (BufferedDodgeTime > 0.0f && bCanDodge)
{
ExecuteDodge();
BufferedDodgeTime = 0.0f;
return;
}
if (bDodgePressedThisFrame)
{
BufferedDodgeTime = DodgeBufferDuration;
}
}