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;
}
}