Cubealingus

Game Info:

Roles: Game Programmer

Date: Aug – Sep 2020

Time: 4 Weeks

Team Size: 9 People (3 Programmers, 3 Game Designers, 3 3D artists)

Genre: Puzzle platformer

Engine: Unreal

Version Control: GitHub

Code Language: C++

Cubalingus is a bizarre and surreal puzzle platformer in which you control an imp trying to escape from hell.

Challenge: Create a puzzle game inspired by the painting The Garden of Earthly Delights (https://en.wikipedia.org/wiki/The_Garden_of_Earthly_Delights).

My Contributions:

Flesh Cube

In the game you pick up and rotate cubes that can interact with other cubes and objects in the world.

I created this cube and made a system for the technical designer to be able to create cube sides in engine and to define the behaviour of the cube in unreals blueprint editor.

Code example - FleshCube.cpp
#include "FleshCube.h"

AFleshCube::AFleshCube()
{
	PrimaryActorTick.bCanEverTick = true;

	SetupBaseMesh();
	SetupSideMeshes();
}

#pragma region Unreal Methods

void AFleshCube::OnConstruction(const FTransform& Transform)
{
	Super::OnConstruction(Transform);
	SetupSides();
}

void AFleshCube::BeginPlay()
{
	Super::BeginPlay();
	this->SetActorTickEnabled(false);

	LeftSide->Initialize_Side(EyeDataLeftSide, SnotScaleLeftSide);
	FrontSide->Initialize_Side(EyeDataFrontSide, SnotScaleFrontSide);
	RightSide->Initialize_Side(EyeDataRightSide, SnotScaleRightSide);
	BackSide->Initialize_Side(EyeDataBackSide, SnotScaleBackSide);
}

void AFleshCube::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	FHitResult GroundCheckHitResult;

	FCollisionQueryParams CollisionParams;
	CollisionParams.AddIgnoredActor(this);

	if (GetWorld()->LineTraceSingleByChannel(GroundCheckHitResult, this->GetActorLocation(), this->GetActorLocation() + (FVector::DownVector * 10000), ECC_Visibility, CollisionParams))
	{
		if (FVector::Distance(this->GetActorLocation(), GroundCheckHitResult.ImpactPoint) < CubeGroundTraceDistance)
		{
			HitGround();
			FHitResult CubeHitResult;
			TryToFindCubeNeighbour(CubeHitResult, LeftSideMeshComponent, CollisionParams, LeftSide, LeftSideType);
			TryToFindCubeNeighbour(CubeHitResult, FrontSideMeshComponent, CollisionParams, FrontSide, FrontSideType);
			TryToFindCubeNeighbour(CubeHitResult, RightSideMeshComponent, CollisionParams, RightSide, RightSideType);
			TryToFindCubeNeighbour(CubeHitResult, BackSideMeshComponent, CollisionParams, BackSide, BackSideType);
			IPickupAble::Execute_OnGrounded(this, this);

			bCanSendStartSignal = false;
			this->SetActorTickEnabled(false);
		}
	}
}

#pragma endregion

#pragma region Event Implementations

void AFleshCube::HitGround_Implementation()
{
}

void AFleshCube::OnPickUp_Implementation(AActor* Caller, FVector ImpactComponent)
{
	AInteractableBase::OnPickUp_Implementation(Caller, ImpactComponent);

	bCurrentlyCarried = true;
	bCanSendStartSignal = false;
	SetActorTickEnabled(false);

	for (UFleshCubeSideBase* Side : ActivatedSides)
	{
		if (Side != nullptr)
		{
			Side->ReceivedStopSignal();
		}
	}

	ActivatedSides.Empty();

	if (LeftSide != nullptr)
	{
		LeftSide->ReceivedStopSignal();
	}
	if (FrontSide != nullptr)
	{
		FrontSide->ReceivedStopSignal();
	}
	if (RightSide != nullptr)
	{
		RightSide->ReceivedStopSignal();
	}
	if (BackSide != nullptr)
	{
		BackSide->ReceivedStopSignal();
	}
}

void AFleshCube::OnDropPickUp_Implementation(AActor* Caller)
{
	AInteractableBase::OnDropPickUp_Implementation(Caller);
	bCurrentlyCarried = false;

	bCanSendStartSignal = true;
	bHasLatched = false;
	SetActorTickEnabled(true);
}

#pragma endregion

#pragma region Setup Sides And Base Mesh

#pragma region Meshes
void AFleshCube::SetupBaseMesh()
{
	BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BoxBase"));
	this->SetRootComponent(BaseMesh);

	BaseMesh->SetSimulatePhysics(true);
}

void AFleshCube::SetupSideMeshes()
{
	SetupSideMesh(LeftSideMeshComponent, BaseMesh, FVector(0.0), FRotator(0.0f, 270.f, 0.0f), FName("Left Side Mesh"));
	SetupSideMesh(FrontSideMeshComponent, BaseMesh, FVector(0.0), FRotator(0.0f), FName("Front Side Mesh"));
	SetupSideMesh(RightSideMeshComponent, BaseMesh, FVector(0.0), FRotator(0.0f, 90.f, 0.0f), FName("Right Side Mesh"));
	SetupSideMesh(BackSideMeshComponent, BaseMesh, FVector(0.0), FRotator(0.0f, 180.f, 0.0f), FName("Back Side Mesh"));
	SetupSideMesh(TopSideMeshComponent, BaseMesh, FVector(0.0), FRotator(90.0f, 00.f, 0.0f), FName("Top Side Mesh"));
	SetupSideMesh(BottomSideMeshComponent, BaseMesh, FVector(0.0), FRotator(-90.0f, 0.f, 0.0f), FName("Bottom Side Mesh"));
}

void AFleshCube::SetupSideMesh(USkeletalMeshComponent*& MeshComponent, UStaticMeshComponent* ComponentParent, FVector ComponentLocation, FRotator ComponentRotation, FName ComponentName)
{
	MeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(ComponentName);
	MeshComponent->AttachToComponent(ComponentParent, FAttachmentTransformRules::KeepRelativeTransform);
	MeshComponent->SetRelativeLocation(ComponentLocation);
	MeshComponent->SetRelativeRotation(ComponentRotation);
}
#pragma endregion

#pragma region Sides
void AFleshCube::SetupSides()
{
	SetupStartSides();

	if (FaceData == nullptr)
	{
		return;
	}

	SetupSide(LeftSideMeshComponent, LeftSideType, PreviousLeftSide, LeftSide, EyeDataLeftSide, SnotScaleLeftSide);
	SetupSide(FrontSideMeshComponent, FrontSideType, PreviousFrontSide, FrontSide, EyeDataFrontSide, SnotScaleFrontSide);
	SetupSide(RightSideMeshComponent, RightSideType, PreviousRightSide, RightSide, EyeDataRightSide, SnotScaleRightSide);
	SetupSide(BackSideMeshComponent, BackSideType, PreviousBackSide, BackSide, EyeDataBackSide, SnotScaleRightSide);
}

void AFleshCube::SetupStartSides()
{
	if (LeftSideType == ESideType::NoUse)
	{
		LeftSideType = ESideType::None;
	}
	if (FrontSideType == ESideType::NoUse)
	{
		FrontSideType = ESideType::None;
	}
	if (RightSideType == ESideType::NoUse)
	{
		RightSideType = ESideType::None;
	}
	if (BackSideType == ESideType::NoUse)
	{
		BackSideType = ESideType::None;
	}

	if (FaceData == nullptr)
	{
		return;
	}

	UFleshCubeSideBase* TemporaryNoneReference = NewObject<UFleshCubeSideBase>(this, FaceData->SideData[ESideType::None].Blueprint);

	if (TemporaryNoneReference == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("Could not find a blueprint to sidetype None"));
		return;
	}

	USkeletalMesh* TopAndBottomFaceMesh = TemporaryNoneReference->GetFaceMesh();
	UMaterialInstance* TopAndBottomMaterial = TemporaryNoneReference->GetFaceMaterial();

	if (TopSideMeshComponent != nullptr)
	{
		TopSideMeshComponent->SetSkeletalMesh(TopAndBottomFaceMesh);
		TopSideMeshComponent->SetMaterial(0, TopAndBottomMaterial);
	}
	if (BottomSideMeshComponent != nullptr)
	{
		if (bCanPickup || BoltedMesh == nullptr)
		{
			BottomSideMeshComponent->SetSkeletalMesh(TopAndBottomFaceMesh);
			BottomSideMeshComponent->SetMaterial(0, TopAndBottomMaterial);
		}
		else
		{
			BottomSideMeshComponent->SetSkeletalMesh(BoltedMesh);
			if (BoltedMaterial != nullptr)
			{
				BottomSideMeshComponent->SetMaterial(0, BoltedMaterial);
				UE_LOG(LogTemp, Warning, TEXT("Bolted Material is not assigned"));
			}
		}
	}

	TemporaryNoneReference->DestroyComponent(false);

	bStartSidesGenerated = true;
}

void AFleshCube::SetupSide(USkeletalMeshComponent*& SideMeshComponent, ESideType& SideType, ESideType& PreviousType, UFleshCubeSideBase*& CubeSide, FEyeComponentData EyeComponentData, FVector SnotScale)
{
	if (SideType != PreviousType)
	{
		TSubclassOf<UFleshCubeSideBase> CubeObject = nullptr;

		if (!FaceData->SideData.Contains(SideType))
		{
			SideType = ESideType::None;
			SetupSide(SideMeshComponent, SideType, PreviousType, CubeSide, EyeComponentData, SnotScale);
			return;
		}
		else
		{
			CubeObject = FaceData->SideData[SideType].Blueprint;
		}

		if (CubeSide != nullptr)
		{
			SideMeshComponent->SetSkeletalMesh(nullptr);
			CubeSide->bEditableWhenInherited = false;
			CubeSide->UnregisterComponent();
			CubeSide->DestroyComponent(false);
		}

		if (CubeObject == nullptr)
		{
			SideMeshComponent->SetSkeletalMesh(nullptr);
			CubeSide = nullptr;
		}
		else
		{
			UFleshCubeSideBase* FleshCubeSide = NewObject<UFleshCubeSideBase>(this, FaceData->SideData[SideType].Blueprint);

			if (FleshCubeSide == nullptr)
			{
				return;
			}

			CubeSide = FleshCubeSide;
			CubeSide->RegisterComponent();
			CubeSide->bEditableWhenInherited = false;

			USkeletalMesh* MeshToUse = CubeSide->GetFaceMesh();
			UMaterialInstance* MaterialToUse = CubeSide->GetFaceMaterial();

			if (SideMeshComponent == nullptr || CubeSide == nullptr)
			{
				return;
			}

			SideMeshComponent->SetSkeletalMesh(MeshToUse);
			SideMeshComponent->SetMaterial(0, MaterialToUse);
			SideMeshComponent->AnimClass = CubeSide->GetAnimationInstance();
		}

		CubeSide->SetCurrentSideType(SideType);
		CubeSide->Initialize_Side(EyeComponentData, SnotScale);
		PreviousType = SideType;
	}
}
#pragma endregion

#pragma endregion

#pragma region Activation Signals

void AFleshCube::SendActivationSignal(AFleshCube* SendingCube, UFleshCubeSideBase* SendingSide, UFleshCubeSideBase* ReceivingSide, ESideType SendingType, bool ReturnSignal)
{
	bool bFoundaSide = false;

	if (ReceivingSide != LeftSide)
	{
		LeftSide->ReceivedActivationSignal(SendingSide, SendingType, LeftSideMeshComponent, LeftSideMeshComponent->GetComponentToWorld());
		bFoundaSide = true;
	}

	if (ReceivingSide != FrontSide)
	{
		FrontSide->ReceivedActivationSignal(SendingSide, SendingType, FrontSideMeshComponent, FrontSideMeshComponent->GetComponentToWorld());
		bFoundaSide = true;
	}

	if (ReceivingSide != RightSide)
	{
		RightSide->ReceivedActivationSignal(SendingSide, SendingType, RightSideMeshComponent, RightSideMeshComponent->GetComponentToWorld());
		bFoundaSide = true;
	}

	if (ReceivingSide != BackSide)
	{
		BackSide->ReceivedActivationSignal(SendingSide, SendingType, BackSideMeshComponent, BackSideMeshComponent->GetComponentToWorld());
		bFoundaSide = true;
	}
}

void AFleshCube::ReceiveRemoteActivationSignal(FString ColliderName)
{
	UFleshCubeSideBase* CurrentSide = GetCubeSideByComponentName(ColliderName);

	if (CurrentSide != nullptr)
	{
		if (FaceData->SideData[CurrentSide->GetCurrentSideType()].bCanBeActivatedByPoop)
		{
			FCollisionQueryParams CollisionParams;
			CollisionParams.AddIgnoredActor(this);

			FHitResult CubeHitResult;

			if (CurrentSide != LeftSide)
			{
				LeftSide->ReceivedActivationSignal(nullptr, ESideType::None, LeftSideMeshComponent, LeftSideMeshComponent->GetComponentToWorld());
			}

			if (CurrentSide != FrontSide)
			{
				FrontSide->ReceivedActivationSignal(nullptr, ESideType::None, FrontSideMeshComponent, FrontSideMeshComponent->GetComponentToWorld());
			}

			if (CurrentSide != RightSide)
			{
				RightSide->ReceivedActivationSignal(nullptr, ESideType::None, RightSideMeshComponent, RightSideMeshComponent->GetComponentToWorld());
			}

			if (CurrentSide != BackSide)
			{
				BackSide->ReceivedActivationSignal(nullptr, ESideType::None, BackSideMeshComponent, BackSideMeshComponent->GetComponentToWorld());
			}
		}
	}
}

#pragma endregion

#pragma region Cube Placement

void AFleshCube::TryToFindCubeNeighbour(FHitResult& CubeHitResult, USkeletalMeshComponent* MeshComponent, FCollisionQueryParams& CollisionParams, UFleshCubeSideBase* SendingSide, ESideType SideType)
{
	FVector StartLocation = MeshComponent->GetComponentLocation();
	FVector EndLocation = StartLocation + (MeshComponent->GetForwardVector() * CubeSideTraceDistance);

	if (GetWorld()->LineTraceSingleByChannel(CubeHitResult, StartLocation, EndLocation, ECC_Visibility, CollisionParams))
	{
		if (CubeHitResult.Actor->IsA<AFleshCube>())
		{
			AFleshCube* OtherCube = Cast<AFleshCube>(CubeHitResult.Actor);
			UFleshCubeSideBase* CurrentSide = OtherCube->GetCubeSideByComponentName(CubeHitResult.Component->GetName());

			if (CurrentSide != nullptr)
			{
				if (FaceData->SideData[CurrentSide->GetCurrentSideType()].FaceMatches.Contains(SideType))
				{
					OtherCube->SendActivationSignal(this, SendingSide, CurrentSide, SideType);
					ActivatedSides.Add(CurrentSide);
				}
				else
				{
					if (FaceData->SideData[SendingSide->GetCurrentSideType()].FaceMatches.Contains(CurrentSide->GetCurrentSideType()))
					{
						SendActivationSignal(OtherCube, CurrentSide, SendingSide, CurrentSide->GetCurrentSideType());
						OtherCube->ActivatedSides.Add(SendingSide);
					}
					else
					{
						const UEnum* SideTypeEnum = FindObject<UEnum>(ANY_PACKAGE, TEXT("ESideType"));
					}
				}
				if (bHasLatched == false)
				{
					LatchCube(MeshComponent, OtherCube->GetFaceMeshByColliderName(CubeHitResult.Component->GetName()));
				}
			}
		}
	}
}

void AFleshCube::LatchCube(USkeletalMeshComponent* LatchingCube, USkeletalMeshComponent* CubeSide)
{
	bHasLatched = true;

	FVector StartLocation = LatchingCube->GetComponentLocation();
	FVector StartForward = LatchingCube->GetForwardVector();
	StartForward.Normalize();

	FVector HitLocation = CubeSide->GetComponentLocation();
	FVector HitForward = CubeSide->GetForwardVector();
	HitForward.Normalize();

	float Distance = FVector::Distance(StartLocation, HitLocation);
	FVector NewLocation = HitLocation + (HitForward * Distance);
	SetActorLocation(NewLocation);

	float Radiants = FMath::Acos(FVector::DotProduct(-StartForward, HitForward));
	float Degree = FMath::RadiansToDegrees(FVector::DotProduct(LatchingCube->GetRightVector(), HitForward) < 0 ? Radiants : -Radiants);
	SetActorRotation(GetActorRotation() + FRotator(0.0f, Degree, 0.0f));
}

#pragma endregion

UFleshCubeSideBase* AFleshCube::GetCubeSideByComponentName(FString ColliderName)
{
	if (ColliderName.Contains(FString("Left")))
	{
		return LeftSide;
	}
	else if (ColliderName.Contains(FString("Front")))
	{
		return FrontSide;
	}
	else if (ColliderName.Contains(FString("Right")))
	{
		return RightSide;
	}
	else if (ColliderName.Contains(FString("Back")))
	{
		return BackSide;
	}
	else
	{
		return nullptr;
	}
}

USkeletalMeshComponent* AFleshCube::GetFaceMeshByColliderName(FString ColliderName)
{
	if (ColliderName.Contains(FString("Left")))
	{
		return LeftSideMeshComponent;
	}
	else if (ColliderName.Contains(FString("Front")))
	{
		return FrontSideMeshComponent;
	}
	else if (ColliderName.Contains(FString("Right")))
	{
		return RightSideMeshComponent;
	}
	else if (ColliderName.Contains(FString("Back")))
	{
		return BackSideMeshComponent;
	}
	else
	{
		return nullptr;
	}
}