The Survival Crafting Challenge
Survival crafting games are uniquely complex because everything connects to everything. The player gathers resources, which feed into crafting, which produces tools and structures, which enable further gathering, which sustains survival needs, which are affected by weather and time of day. One poorly designed system creates cascading problems across the entire game.
The key to managing this complexity is modular architecture — independent systems that communicate through clean interfaces, not direct dependencies.
System Architecture Overview
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Survival │ │ Resource │ │ Crafting │
│ Needs │◄──►│ System │◄──►│ System │
│ (HP/Hunger/ │ │ (Inventory │ │ (Recipes, │
│ Thirst/ │ │ Gathering │ │ Workbench)│
│ Temp) │ │ Storage) │ │ │
└──────┬───────┘ └──────┬───────┘ └──────┬──────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Environment │ │ Building │ │ Equipment │
│ (Weather, │ │ System │ │ System │
│ Day/Night, │ │ (Placement, │ │ (Weapons, │
│ Biomes) │ │ Stability) │ │ Armor, │
│ │ │ │ │ Tools) │
└─────────────┘ └──────────────┘ └─────────────┘
Each box is a component or subsystem that communicates through delegates and interfaces, not hard references.
Core System: Resource and Inventory
The resource system is the foundation everything else builds on.
Resource Data Architecture
USTRUCT(BlueprintType)
struct FResourceData : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
FName ResourceId;
UPROPERTY(EditAnywhere)
FText DisplayName;
UPROPERTY(EditAnywhere)
int32 MaxStackSize = 99;
UPROPERTY(EditAnywhere)
float Weight = 0.1f;
UPROPERTY(EditAnywhere)
FGameplayTagContainer ResourceTags; // e.g., "Resource.Wood", "Resource.Fuel"
UPROPERTY(EditAnywhere)
TSoftObjectPtr<UTexture2D> Icon;
};
Use Data Tables for resource definitions — designers can add new resources without code changes.
Inventory Component
UCLASS()
class UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FOnInventoryChanged OnInventoryChanged;
UFUNCTION(BlueprintCallable)
bool AddItem(FName ResourceId, int32 Count);
UFUNCTION(BlueprintCallable)
bool RemoveItem(FName ResourceId, int32 Count);
UFUNCTION(BlueprintCallable)
int32 GetItemCount(FName ResourceId) const;
UFUNCTION(BlueprintCallable)
bool HasItems(const TArray<FResourceRequirement>& Requirements) const;
UFUNCTION(BlueprintCallable)
float GetTotalWeight() const;
private:
UPROPERTY(Replicated)
TArray<FInventorySlot> Slots;
UPROPERTY(EditDefaultsOnly)
int32 MaxSlots = 30;
UPROPERTY(EditDefaultsOnly)
float MaxWeight = 100.f;
};
The inventory communicates changes through the OnInventoryChanged delegate. Other systems listen without direct coupling.
Gatherable Resources
Resources in the world implement an interface:
UINTERFACE(BlueprintType)
class UGatherable : public UInterface { GENERATED_BODY() };
class IGatherable
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent)
FGatherResult Gather(AActor* Gatherer, FName ToolType);
UFUNCTION(BlueprintNativeEvent)
float GetGatherTime() const;
UFUNCTION(BlueprintNativeEvent)
bool CanGather(AActor* Gatherer) const;
};
A tree, a rock, a berry bush — all implement IGatherable with different drops, gather times, and tool requirements. The gathering system doesn't need to know what it's gathering; it just calls the interface.
Crafting System
Recipe Data
USTRUCT(BlueprintType)
struct FCraftingRecipe : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
FName RecipeId;
UPROPERTY(EditAnywhere)
FText DisplayName;
UPROPERTY(EditAnywhere)
TArray<FResourceRequirement> Ingredients;
UPROPERTY(EditAnywhere)
TArray<FResourceRequirement> Results;
UPROPERTY(EditAnywhere)
float CraftTime = 2.0f;
UPROPERTY(EditAnywhere)
FName RequiredStation; // "None" for hand crafting, "Workbench", "Forge", etc.
UPROPERTY(EditAnywhere)
FGameplayTagContainer RequiredTags; // Skills or progression gates
};
Crafting Component
UCLASS()
class UCraftingComponent : public UActorComponent
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
bool CanCraft(const FCraftingRecipe& Recipe) const;
UFUNCTION(BlueprintCallable)
void StartCrafting(const FCraftingRecipe& Recipe);
UFUNCTION(BlueprintCallable)
TArray<FCraftingRecipe> GetAvailableRecipes() const;
private:
bool CheckIngredients(const FCraftingRecipe& Recipe) const;
bool CheckStation(const FCraftingRecipe& Recipe) const;
UPROPERTY()
UInventoryComponent* OwnerInventory; // Found via GetOwner()
FTimerHandle CraftTimer;
};
Building System
Grid-Based Placement
Most survival games use a grid-based building system:
UCLASS()
class UBuildingComponent : public UActorComponent
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void StartPlacement(TSubclassOf<ABuildingPiece> PieceClass);
UFUNCTION(BlueprintCallable)
void ConfirmPlacement();
UFUNCTION(BlueprintCallable)
void CancelPlacement();
UFUNCTION(BlueprintCallable)
void RotatePlacement(float Degrees);
private:
void UpdatePlacementPreview();
bool IsPlacementValid() const;
FVector SnapToGrid(FVector WorldLocation) const;
UPROPERTY()
ABuildingPiece* PlacementPreview;
UPROPERTY(EditDefaultsOnly)
float GridSize = 100.f; // Snap grid in units
};
Building Piece Base Class
UCLASS()
class ABuildingPiece : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly)
TArray<FResourceRequirement> BuildCost;
UPROPERTY(EditDefaultsOnly)
float MaxHealth = 500.f;
UPROPERTY(EditDefaultsOnly)
EBuildingSocket SocketType; // Foundation, Wall, Ceiling, Ramp
UPROPERTY(EditDefaultsOnly)
TArray<EBuildingSocket> ConnectsTo; // What this piece snaps to
UPROPERTY(ReplicatedUsing = OnRep_CurrentHealth)
float CurrentHealth;
// Snapping points for connecting pieces
UPROPERTY(EditDefaultsOnly)
TArray<FBuildingSnapPoint> SnapPoints;
};
Structural Stability
Buildings need structural integrity — floating structures shouldn't be allowed:
bool ABuildingPiece::HasStructuralSupport() const
{
// Foundation pieces are always supported
if (SocketType == EBuildingSocket::Foundation)
return true;
// Other pieces need a connection to a supported piece
for (const ABuildingPiece* Connected : ConnectedPieces)
{
if (Connected && Connected->HasStructuralSupport())
return true;
}
return false;
}
When a supporting piece is destroyed, check all connected pieces and collapse unsupported ones.
Survival Needs System
Need Component
UCLASS()
class USurvivalNeedComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FOnNeedChanged OnNeedChanged;
UPROPERTY(BlueprintAssignable)
FOnNeedCritical OnNeedCritical; // When need reaches danger threshold
UFUNCTION(BlueprintCallable)
void ModifyNeed(float Amount); // Positive = satisfy, Negative = deplete
UFUNCTION(BlueprintPure)
float GetNeedPercent() const { return CurrentValue / MaxValue; }
private:
virtual void TickComponent(float DeltaTime, ...) override;
UPROPERTY(EditDefaultsOnly)
float MaxValue = 100.f;
UPROPERTY(Replicated)
float CurrentValue;
UPROPERTY(EditDefaultsOnly)
float DepletionRate = 1.0f; // Per second base rate
UPROPERTY(EditDefaultsOnly)
float DangerThreshold = 0.2f; // 20% triggers warning
};
Temperature System
Temperature adds environmental depth:
UCLASS()
class UTemperatureComponent : public UActorComponent
{
GENERATED_BODY()
public:
float GetEffectiveTemperature() const;
private:
float CalculateEnvironmentTemperature() const;
float CalculateClothingInsulation() const;
float CalculateProximityToHeatSources() const;
// Temperature affects other needs
void ApplyTemperatureEffects(float DeltaTime);
// Cold: increased hunger drain, decreased stamina regen
// Hot: increased thirst drain, decreased movement speed
};
Day-Night and Weather
Time System
UCLASS()
class ATimeOfDayManager : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FOnTimeChanged OnTimeChanged;
UPROPERTY(BlueprintAssignable)
FOnDayPhaseChanged OnDayPhaseChanged; // Dawn, Day, Dusk, Night
UFUNCTION(BlueprintPure)
float GetTimeOfDay() const; // 0.0 = midnight, 0.5 = noon
UFUNCTION(BlueprintPure)
EDayPhase GetCurrentPhase() const;
UFUNCTION(BlueprintPure)
int32 GetDayNumber() const;
private:
virtual void Tick(float DeltaTime) override;
UPROPERTY(EditDefaultsOnly)
float DayLengthMinutes = 24.f; // Real-time minutes per game day
UPROPERTY(Replicated)
float CurrentTime; // 0.0 to 1.0
// Updates Directional Light rotation, Sky Light, fog colors
void UpdateWorldLighting();
};
Weather Integration
Weather affects multiple survival systems:
- Rain: Increases cold exposure, reduces fire effectiveness, refills water collectors
- Snow: Severe cold, reduced visibility, slower movement
- Heat wave: Increased thirst drain, heat stroke risk
- Wind: Affects arrow trajectory, increases cold with wind chill
Each weather type broadcasts its state, and individual systems respond independently.
Architecture Principles
Use Gameplay Tags for Cross-System Communication
// Instead of hard-coded checks
if (EquippedItem == "FurCoat") // BAD
// Use tags
if (EquippedTags.HasTag("Clothing.Insulation.Heavy")) // GOOD
Tags let you add new items with insulation properties without modifying the temperature system.
Use Delegates for Decoupling
// Survival need broadcasts that it's critical
OnNeedCritical.Broadcast(ENeedType::Hunger, CurrentPercent);
// UI listens
HungerWidget->BindToNeedCritical(SurvivalComponent);
// Audio listens
AudioManager->PlayWarningSound(ENeedType::Hunger);
// Gameplay effect listens
ApplyHungerDebuff();
Three systems respond to the same event without knowing about each other.
Use Data Tables for Designer Iteration
Put every number that affects game balance in a Data Table:
- Resource gather rates
- Crafting recipe ingredients
- Survival need depletion rates
- Building health values
- Weather effect magnitudes
Designers iterate on balance by editing spreadsheets, not code.
Scaling Considerations
Multiplayer
Survival games are often multiplayer. Design for it from day one:
- Inventory replication with authority on the server
- Building piece ownership and permissions
- Shared vs. private storage containers
- Server-authoritative gathering (prevent duplication exploits)
Performance
Survival worlds have thousands of interactive objects:
- Use HISM for resource nodes (trees, rocks, bushes)
- Pool spawned items instead of creating/destroying
- Chunk-based world streaming for large maps
- LOD aggressively on building pieces
Save System
Survival games need robust save/load for:
- Every placed building piece (position, rotation, health, contents)
- All container inventories
- Player stats and equipment
- World state (gathered resources, time, weather)
- Tamed creatures, vehicles, automation chains
Serialize to a structured format (JSON or binary) and test save/load round-trips early and often. Corrupted saves are the most common complaint in survival game reviews.
Building a survival crafting game is an exercise in systems design. Get the architecture right, and adding new content (resources, recipes, building pieces, biomes) is straightforward. Get it wrong, and every addition becomes a fight against spaghetti code.