Works

Portfolio

Rooftop Garden

Portfolio

Unreal Engine 5 C++ & Blueprints Group Project Single player Farming Simulator Technical Doc Game Design Team Lead

About

Rooftop Garden is a cozy gardening simulator within a post-apocalyptic setting. One thing that makes it stand out from the rest is your uncanny companion: the omnivorous plant, which you must feed before it feeds on you.

Inspired by Stardew Valley and Garden Life, it’s a first-person game with a day-night cycle where you must take care of your crops and manage your resources.

Currently, this project is my most well-structured

As the only programmer in the group, I wrote the entire code base for the project (except for the ladder mechanic, which was programmed by Issa). This is currently my best work in terms of a clean, maintainable, and data-oriented codebase.

Download on itch

Gameplay Video

What I did

Before I mention the technical details of my work, I will start with the non-technical ones.

Team Lead

I was a central figure in the group, since many details had to go through me as the programmer. Before I knew it, I was assigned as the Team Leader.

As such, I played an important role discussing design choices, helping my teammates out, coordinating the technical direction of the project, but also managing the structure and organization of the group and defining the documentation of the project.

The most notable document I worked on would, of course, be the Tech Document.

Now, onto more technical matters...

Project UML Diagram

The following is a full UML diagram of the project.

Below are some of the project's highlights, and some of my most proud work.

Save System

The save system represents the most intricate component of the game. Daily, the condition of every actor eligible for saving is recorded in a dedicated save game object. Upon game launch, if a save game object is present, all previously saved actors are reinstated into the game.

Since there are objects (like pick-ups) that can be moved or destroyed, the system will always check for the existing saveable objects first before loading the saved ones. This is done by checking the objects’ global unique ID.

Check Code

	for (auto& Actor : ExistingSaveableActors)
	{
		FGuid SaveID = ISaveableInterface::Execute_GetSaveID(Actor);
		// Loading data for existing saved actors
		if (SavedActors.Contains(SaveID))
		{
			FSaveStruct& SaveData = SavedActors[SaveID];
			LoadExistingActorState(Actor, SaveData);
			ActorMap.Add(SaveID, Actor);
			ActorsToSpawn.Remove(SaveID);
		}
		// Destroying current actors that are not in the save data
		else Actor->Destroy();
	}

	// Spawning the saved actors that don't exist in the world
	for (auto& Actor : ActorsToSpawn)
	{
		FSaveStruct SaveData = Actor.Value;
		FGuid SaveID = Actor.Key;
		AActor* SpawnedActor = SpawnFromSaveData(SaveData);

		LoadSpawnedActor(SpawnedActor, SaveID, SaveData);
		ActorMap.Add(SaveID, SpawnedActor);
	}
                                    

The system is very modular, with each saveable actor implementing its own save and load methods through the ISaveable interface. Components can also be saved the same way using the ISaveableComponent interface.

The components of all saveable objects are checked for the interface after the owner’s data is restored/saved, and their load/save method is called. This happens within the save game object, in the Load/Save methods.

Check Code

void USaveGameObject::LoadExistingActorState(AActor*& Actor, FSaveStruct& SaveData)
{
	ISaveableInterface::Execute_LoadState(Actor, SaveData.SaveData);
	Actor->SetActorTransform(SaveData.Transform);

	if (SaveData.ComponentData.IsEmpty()) return;

	auto SaveableComponents = Actor->GetComponentsByInterface(USaveableComponentInterface::StaticClass());
	for (auto& Component : SaveableComponents)
	{
		FGuid ComponentSaveID = ISaveableComponentInterface::Execute_GetComponentSaveID(Component);
		if (SaveData.ComponentData.Contains(ComponentSaveID))
		{
			const FComponentSaveStruct& ComponentSaveData = SaveData.ComponentData[ComponentSaveID];
			ISaveableComponentInterface::Execute_LoadComponentState(Component, ComponentSaveData.SaveData);
		}
	}
}
                                    

Relationships between saved objects are also restored one frame after loading, using the same GUIDs.

Check Code

for (auto& ActorPair : ActorMap)
		{
			ISaveableInterface::Execute_RestoreRelationships(ActorPair.Value, ActorMap);
		}

		AActor* PlayerActor = UGameplayStatics::GetPlayerPawn(GWorld, 0);
		auto SaveableComponents = PlayerActor->GetComponentsByInterface(USaveableComponentInterface::StaticClass());
		for (auto& Component : SaveableComponents)
		{
			ISaveableComponentInterface::Execute_RestoreRelationships(Component, ActorMap);
		}
                                    

Description

Inventory System

The system is flexible, data-driven and supports configurable inventory sizes, item stacking, and quick item switching. Each inventory slot stores an item ID, quantity, type, and an actor reference, allowing held items to be visually represented and interacted with directly.

Item pickup prioritizes stacking existing items before filling empty slots, with clear fallback logic when the inventory is full. Item metadata is retrieved from a centralized Data Table, separating game data from inventory logic for improved maintainability and scalability.

The system is fully implemented in C++.

Check Code

bool UInventoryComponent::AddToInventory(const FName& ItemID, int32 Quantity, AActor* ItemActor)
{
	FItemStruct ItemStruct;
	const bool Success = UUtilitiesLibrary::GetActiveItemInfoStructFromID(ItemID, ItemStruct);
	if (!Success) return false;
	int FirstEmptySlotIndex = -1;
	// Tools don't stack, so if active is empty that's where it should be placed
	if (ItemStruct.ItemType == EItemType::Tool && Content[ActiveSlot].ItemID.IsNone())
	{
		FirstEmptySlotIndex = ActiveSlot;
	}
	else
	{
		for (int i{Content.Num() - 1}; i >= 0; --i)
		{
			// Record the first empty slot we find
			if (Content[i].ItemID.IsNone()) FirstEmptySlotIndex = i;
			// Tools don't stack, we just need the first empty slot idx
			if (ItemStruct.ItemType == EItemType::Tool) continue;
			// Check if there is one of these items in the inventory already, if so add to that stack
			if (Content[i].ItemID == ItemID)
			{
				// Do not attach an extra item since you already have it
				SetItemInfoContent({ItemID, Quantity, ItemActor, ItemStruct.ItemType}, i, false);
				OnInventoryChanged.Broadcast(i);
				return true;
			}
		}
	}
	if (FirstEmptySlotIndex == -1)
	{
		OnAddInventoryFailed.Broadcast(FText::FromString("No inventory space"));
		return false; // inventory full
	}

	// If the active slot is empty add item there, otherwise use the first empty slot found
	int SlotToAdd = FirstEmptySlotIndex;
	if (Content[ActiveSlot].ItemID.IsNone()) SlotToAdd = ActiveSlot;
	SetItemInfoContent({ItemID, Quantity, ItemActor, ItemStruct.ItemType}, SlotToAdd);
	OnInventoryChanged.Broadcast(SlotToAdd);
	return true;
}
                                    

Description

Optimizations

A good amount of time was spent making sure the technical goals of the project were reached. These are the optimization methods I made use of:

  • Collision override: I built a blueprint utility script to easily remove collision or add simple collision (box, capsule) to static meshes. Some meshes (like the shelves and baskets) had custom collision made from simple boxes by hand.
  • LODs: The utility blueprint also has a method for generating LODs for meshes that have less than three.
  • Light complexity: using the GPU Lightmass plugin, the light was baked into virtual textures, light sources were minimized and all of them, except for the sun and moon, were made static. For reference, here is a before and after:

Description Description

  • Lumen was disabled, as it messed with the light baking process, and it was also expensive.
  • I also got rid of unnecessary textures and reduced the texture sizes according to the required texture resolution view.

Description Description

Description Description

The team

Producer & Team Lead

Tech Lead

Game Design

Art

Art Lead - Tech & Style

3D Artist

UI Design

Concept & 3D Artist