Indie · Epic MegaJam 2025 · Solo · UE 5.6

Contract Renewed

Contract Renewed

A burnt-out employee trapped in a fake Motivational Simulation Program™ — a 2D character moving through a 3D world. Every run you fail, HR reboots you into a new department. Built solo in 6 days for the Epic MegaJam One Man Army modifier.

GAS Paper2D in 3D Procedural Hex Gen Behavior Trees FastNoise ISM C++
What I Built
  • Full GAS stack — replicated AttributeSet (Health, Stamina, AttackPower, Damage meta-attribute), custom GameplayEffects, damage execution pipeline.
  • Procedural hex islands — 3-layer noise blend + radial falloff + voxel-quantised heights; biome-aware prop scatter with navmesh-ready retry logic.
  • Paper2D in 3D — camera-space velocity flipbook system, networked multicast punch anims, bitfield state gates for attack & footstep timing.
  • AI enemies — Behavior Tree + custom BTTask_FindRandomLocation, AIPerception, launch-force hit response.
  • Two levels, fake corporate rank progression, custom VFX, SFX, and ambient loops.

GAS Attribute Set

One macro generates the full getter/setter/initter interface per attribute. The transient Damage meta-attribute is never replicated — it's consumed by the damage execution calc and converted to negative Health.

HopperAttributeSet.h
// One macro line = 4 accessors generated automatically
#define ATTRIBUTE_ACCESSORS(ClassName, Prop)          \
    GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, Prop) \
    GAMEPLAYATTRIBUTE_VALUE_GETTER(Prop)               \
    GAMEPLAYATTRIBUTE_VALUE_SETTER(Prop)               \
    GAMEPLAYATTRIBUTE_VALUE_INITTER(Prop)

UCLASS()
class UHopperAttributeSet : public UAttributeSet
{
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Health)
    FGameplayAttributeData Health;
    ATTRIBUTE_ACCESSORS(UHopperAttributeSet, Health)

    UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_MaxHealth)
    FGameplayAttributeData MaxHealth;
    ATTRIBUTE_ACCESSORS(UHopperAttributeSet, MaxHealth)

    UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Stamina)
    FGameplayAttributeData Stamina;
    ATTRIBUTE_ACCESSORS(UHopperAttributeSet, Stamina)

    // Transient — consumed by DamageExecution, never replicated
    UPROPERTY(BlueprintReadOnly)
    FGameplayAttributeData Damage;
    ATTRIBUTE_ACCESSORS(UHopperAttributeSet, Damage)

    virtual void PostGameplayEffectExecute(
        const FGameplayEffectModCallbackData& Data) override;
};
Procedural Hex Island Generator

3-layer noise blend (large 0.8× + detail 4×), radial falloff clamps island edges, voxel-snap rounds heights to 50-unit steps. Water vs grass determined by sign of final height.

HexManager.cpp — GenerateHexGrid()
const float MaxDist = FMath::Min(GridHalfW, GridHalfH) * 0.95f;

for (int32 y = 0; y < GridHeight; ++y)
for (int32 x = 0; x < GridWidth;  ++x)
{
    const float XPos = (y%2) ? x*HOffset+OddOff : x*HOffset;
    const float YPos = y * VOffset;
    const float Dist = FVector2D::Distance({XPos,YPos}, Center);
    if (Dist > MaxDist * 1.05f) continue; // island mask

    // 3-layer blend: shape(0.8x) 70% + detail(4x) 30%
    const float Large  = Noise->GetNoise2D(XPos*.8f+S, YPos*.8f+S);
    const float Detail = Noise->GetNoise2D(XPos*4.f+DS, YPos*4.f+DS);
    const float Blend  = Large*.7f + Detail*.3f;
    const float Fall   = FMath::Clamp(1.f-(Dist/MaxDist),0.f,1.f);

    float H = (Blend*.8f + Fall*.3f) * HeightStrength;
    H = FMath::RoundToFloat(H/50.f)*50.f; // voxel step

    // grass above sea, water below — one HISM per biome
    ((H >= 0.f) ? GrassMesh : WaterMesh)->AddInstance(
        FTransform(FVector(XPos, YPos, H)));
}
Terrain-Aligned Prop Scatter + Glitch Material

Props cluster around a central tile; a line trace snaps each to terrain geometry. 25% chance applies the glitch material and tilts the prop — intentionally broken, corporate-simulation aesthetic.

HexManager.cpp — SpawnAllActors()
for (int32 j = 0; j < ClusterSize; ++j)
{
    FVector Loc = ClusterCenter
                + FMath::VRand() * FMath::RandRange(80.f,150.f)
                + FVector(0,0, HeightOff);

    const bool bGlitch = FMath::FRand() < 0.25f;
    if (bGlitch) { Loc.Z -= 50.f; Rot.Pitch += Rand(-30,30); }

    // Snap to terrain via line trace
    FHitResult Hit;
    if (World->LineTraceSingleByChannel(Hit,
            Loc+FVector(0,0,500), Loc-FVector(0,0,1000),
            ECC_WorldStatic))
    {
        FRotator SurfRot = Hit.ImpactNormal.Rotation();
        FRotator YawRot(0, FMath::FRandRange(0.f,360.f), 0);
        Rot = (SurfRot.Quaternion() * YawRot.Quaternion()).Rotator();
        Rot.Pitch *= .5f; Rot.Roll *= .5f; // keep props upright-ish
        Loc = Hit.ImpactPoint;
    }

    if (AActor* A = World->SpawnActor<AActor>(Data.Class, Loc, Rot))
    {
        SpawnedActors.Add(A);
        if (bGlitch && GlitchMat)
            for (UStaticMeshComponent* M :
                    TComponentRange<UStaticMeshComponent>(A))
                M->SetMaterial(0, GlitchMat);
    }
}
Screenshots
Contract Renewed screenshot 2
Contract Renewed screenshot 3
Contract Renewed screenshot 4
Jam Epic MegaJam 2025 — One Man Army Duration 6 days · solo Engine Unreal Engine 5.6 Language C++ Category Indie · Platformer · Roguelite Play khaledelsayed.itch.io ↗ Source github.com/khaled71612000 ↗
Connect