Ursa Minor is a puzzle/adventure game in which the player takes the role of the "Little Bear" in a quest to get back to safety.


The player will have to solve different puzzle using the little bear's power of controlling the light direction to stay in shadow and not get harmed by the strong star light.


My Role:


Character position detection(in relation to the directional light),

Object Grabbing Mechanic,

Health/Damage System.



Project Length: 4 weeks

Team Size: 12 members

Engine: Unreal Engine 4

Language: C++

Charachter Position Detection:

The detection is done with a line trace done to five different points along the body of the bear. This was done to have a more accurate check on which part of the bear's body was not in the shadow.


Code:

          	
TArray< USceneComponent*> PointsToTrace;

UPROPERTY(EditDefaultsOnly, Category = TracePoints)
class USceneComponent* HeadTraceComp;

UPROPERTY(EditDefaultsOnly, Category = TracePoints)
class USceneComponent* LeftShoulderTraceComp;

UPROPERTY(EditDefaultsOnly, Category = TracePoints)
class USceneComponent* RightShoulderTraceComp;

UPROPERTY(EditDefaultsOnly, Category = TracePoints)
class USceneComponent* LeftLegTraceComp;

UPROPERTY(EditDefaultsOnly, Category = TracePoints)
class USceneComponent* RightLegTraceComp;
    	    
    	
      
    //Trace Points	
	HeadTraceComp = ObjectInitializer.CreateDefaultSubobject< USceneComponent>(this, TEXT("Head"));
	LeftShoulderTraceComp = ObjectInitializer.CreateDefaultSubobject< USceneComponent>(this, TEXT("LeftShoulder"));
	RightShoulderTraceComp = ObjectInitializer.CreateDefaultSubobject< USceneComponent>(this, TEXT("RightShoulder"));
	LeftLegTraceComp = ObjectInitializer.CreateDefaultSubobject< USceneComponent>(this, TEXT("LeftLeg"));
	RightLegTraceComp = ObjectInitializer.CreateDefaultSubobject< USceneComponent>(this, TEXT("RightLeg"));

	PointsToTrace.Add(HeadTraceComp);
	PointsToTrace.Add(LeftShoulderTraceComp);
	PointsToTrace.Add(RightShoulderTraceComp);
	PointsToTrace.Add(LeftLegTraceComp);
	PointsToTrace.Add(RightLegTraceComp);

	HeadTraceComp->SetupAttachment(RootComponent);
	LeftShoulderTraceComp->SetupAttachment(RootComponent);
	RightShoulderTraceComp->SetupAttachment(RootComponent);
	LeftLegTraceComp->SetupAttachment(RootComponent);
	RightLegTraceComp->SetupAttachment(RootComponent);
	
	/**/
	
	void AFG_PlayerCharacter::DrawLightSourceTrace()
    {
    	TArray< FHitResult> MultilineHitResult;
    
    	FCollisionQueryParams TraceParams = FCollisionQueryParams(false);
    	TraceParams.bTraceAsyncScene = true;
    	TraceParams.bReturnPhysicalMaterial = false;
    	TraceParams.AddIgnoredActor(this);
    
    	FVector StartPosition;
    	FVector LightSourceDirection;
    	FVector EndPosition;
    
    	bIsInShadow = false;
    
    	for (USceneComponent* Point : PointsToTrace)
    	{
    		StartPosition = Point->GetComponentLocation();
    		LightSourceDirection = -LightSource->GetActorForwardVector() * 20000.0f;
    		EndPosition = LightSourceDirection + StartPosition;
    
    		GetWorld()->LineTraceMultiByChannel(MultilineHitResult, StartPosition, EndPosition, ECC_WorldDynamic, TraceParams);
    
    		for (FHitResult Object : MultilineHitResult)
    		{
    			if (Object.bBlockingHit)
    			{
    				bIsInShadow = true;
    				break;
    			}
    		}
    	}
    }

    void AFG_PlayerCharacter::LightSourceCheck()
    {
	    FTimerHandle TimerHandle;
	    GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &AFG_PlayerCharacter::DrawLightSourceTrace, 0.25f, true);
    }
    
	

Object Grabbing Mechanic:

This mechanic allows the player to pick up and move physical object, that can be use to activate platforms or keep in shadow


Code:

          	
    bool bIsPhysicsHandleActive;
	UPROPERTY(BlueprintReadWrite)
	bool bIsHoldingObject;
	float OtherItemPosition = 0.0f;
	FVector PhysicsHandlePosition;
	FRotator OtherItemRotation;
	
	UPROPERTY(EditAnywhere, Category = OBjectHolding)
		float DistToMoveObject = 10.f;
	
	UPROPERTY(EditAnywhere, Category = GrabDistance)
	float GrabDistance = 900.f;

	TArray< TEnumAsByte< EObjectTypeQuery>> ObjectsToLookFor;

	// Reference to the object being grabbed
	AActor* GrabbedObject = nullptr;

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite, Category = Pickup)
	class UPhysicsHandleComponent* PhysicsHandleComp = nullptr;

	class AFG_LightAffectableActor* GrabbedLightAffectableObjectPtr = nullptr;
    	    
    	
      
       void AFG_PlayerCharacter::GrabObjectPressed()
    {
    	if (bIsHoldingObject)
    	{
    		GrabObjectReleased();
    		return;
    	}
    	FCollisionQueryParams GrabTraceParams = FCollisionQueryParams(true);
    	GrabTraceParams.bTraceAsyncScene = true;
    	GrabTraceParams.bReturnPhysicalMaterial = false;
    	FHitResult GrabHitResult;
    
    	FVector StartPosition = CameraComponent->GetComponentLocation();
    	FVector TraceLength = CameraComponent->GetForwardVector() * 1000;
    	FVector EndPosition = StartPosition + TraceLength;
    	
    	GetWorld()->LineTraceSingleByObjectType(GrabHitResult, StartPosition, EndPosition, ObjectsToLookFor, GrabTraceParams);
    	OtherItemPosition = (CameraComponent->GetComponentLocation() - GrabHitResult.Location).Size();
    	OtherItemRotation = CameraComponent->GetComponentRotation();
    	
    	FHitResult HitBelowPlayer;
	for (USceneComponent* BodyPart : PointsToTrace)
	{
		GetWorld()->LineTraceSingleByObjectType(HitBelowPlayer, BodyPart->GetComponentLocation(),
			BodyPart->GetComponentLocation() + (FVector(0, 0, -1) * 250), ObjectsToLookFor);

		if (HitBelowPlayer.bBlockingHit)
		{
			if (GrabHitResult.GetActor() == HitBelowPlayer.GetActor())
			{
				return;
			}
		}
	}

	if (GrabHitResult.bBlockingHit &&GrabHitResult.Component->GetMass() <= 500.f)
	{
		if (GrabHitResult.GetActor()->IsA< AFG_LightAffectableActor>())
		{
			GrabbedLightAffectableObjectPtr = Cast< AFG_LightAffectableActor>(GrabHitResult.GetActor());
			GrabbedLightAffectableObjectPtr->bIsBeingGrabbed = true;
			GrabbedLightAffectableObjectPtr->MeshComp->SetRenderCustomDepth(true);
		}
		bIsPhysicsHandleActive = true;
		GrabHitResult.Actor->AddComponent(FName("GrabPointComp"), false, GetActorTransform(), UPhysicsHandleComponent::StaticClass());
		//GrabHitResult.Actor->AddComponent(FName("GrabPointComp"), false, GrabHitResult.GetActor()->GetActorTransform(), UPhysicsHandleComponent::StaticClass());
		PhysicsHandleComp->GrabComponentAtLocationWithRotation(GrabHitResult.GetComponent(), GrabHitResult.BoneName, GrabHitResult.Location, OtherItemRotation);
		PhysicsHandleComp->SetTargetLocation(PhysicsHandlePosition + FVector(200.f, 0.f, 0.f));

		UGameplayStatics::PlaySound2D(this, ActionBeginSFX);
		SFXObjHoldingComponent->SetSound(ObjectHoldingLoopSFX);
		SFXObjHoldingComponent->FadeIn(FadeTime, Volume, StartTime);
		bIsHoldingObject = true;
		GrabbedObject = GrabHitResult.GetActor();
		UStaticMeshComponent * StaticMeshPtr = Cast< UStaticMeshComponent>(GrabbedObject->GetComponentByClass(UStaticMeshComponent::StaticClass()));
		StaticMeshPtr->SetRenderCustomDepth(true);
	}
}

void AFG_PlayerCharacter::GrabObjectReleased()
{
	if (bIsPhysicsHandleActive && bIsHoldingObject)
	{
		if (GrabbedLightAffectableObjectPtr != nullptr)
		{
			GrabbedLightAffectableObjectPtr->bIsBeingGrabbed = false;
			GrabbedLightAffectableObjectPtr->MeshComp->SetRenderCustomDepth(false);
			GrabbedLightAffectableObjectPtr = nullptr;
			
		}

		if (PhysicsHandleComp)
		{
			PhysicsHandleComp->ReleaseComponent();
		}
		UGameplayStatics::PlaySound2D(this, ActionEndSFX);
		SFXObjHoldingComponent->FadeOut(FadeTime, 0.0f);
		bIsPhysicsHandleActive = false;
		bIsHoldingObject = false;
		UStaticMeshComponent * StaticMeshPtr = Cast< UStaticMeshComponent>(GrabbedObject->GetComponentByClass(UStaticMeshComponent::StaticClass()));
		StaticMeshPtr->SetRenderCustomDepth(false);

	}
}

void AFG_PlayerCharacter::CancelGrabbingObject()
{
	if (bIsHoldingObject)
	{
		FHitResult HitBelowPlayer;

		for (USceneComponent* BodyPart : PointsToTrace)
		{
			GetWorld()->LineTraceSingleByObjectType(HitBelowPlayer, BodyPart->GetComponentLocation(),
				BodyPart->GetComponentLocation() + (FVector(0, 0, -1) * GrabDistance), ObjectsToLookFor);

			if (HitBelowPlayer.bBlockingHit)
			{
				if (GrabbedObject == HitBelowPlayer.GetActor())
				{
					GrabObjectReleased();
					break;
				}
			}
		}
	}
}
        
	

Health/Damage System:

The health and damage system will be notified whenever the character is exposed to star light and deliver damage, the player will feel an increasing vibration in the game pad and the stars on the charactes body wil start disappearing until health is depleated.


Code:

          	
	bool bIsPhysicsHandleActive;
	UPROPERTY(BlueprintReadWrite)
	bool bIsHoldingObject;
	float OtherItemPosition = 0.0f;
	FVector PhysicsHandlePosition;
	FRotator OtherItemRotation;
	
	UPROPERTY(EditAnywhere, Category = OBjectHolding)
		float DistToMoveObject = 10.f;
	
	UPROPERTY(EditAnywhere, Category = GrabDistance)
	float GrabDistance = 900.f;

	TArray< TEnumAsByte< EObjectTypeQuery>> ObjectsToLookFor;

	// Reference to the object being grabbed
	AActor* GrabbedObject = nullptr;

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite, Category = Pickup)
	class UPhysicsHandleComponent* PhysicsHandleComp = nullptr;

	class AFG_LightAffectableActor* GrabbedLightAffectableObjectPtr = nullptr;
    	    
    	
      
    void AFG_PlayerCharacter::GrabObjectPressed()
    {
    	if (bIsHoldingObject)
    	{
    		GrabObjectReleased();
    		return;
    	}
    	FCollisionQueryParams GrabTraceParams = FCollisionQueryParams(true);
    	GrabTraceParams.bTraceAsyncScene = true;
    	GrabTraceParams.bReturnPhysicalMaterial = false;
    	FHitResult GrabHitResult;
    
    	FVector StartPosition = CameraComponent->GetComponentLocation();
    	FVector TraceLength = CameraComponent->GetForwardVector() * 1000;
    	FVector EndPosition = StartPosition + TraceLength;
    	
    	GetWorld()->LineTraceSingleByObjectType(GrabHitResult, StartPosition, EndPosition, ObjectsToLookFor, GrabTraceParams);
    	OtherItemPosition = (CameraComponent->GetComponentLocation() - GrabHitResult.Location).Size();
    	OtherItemRotation = CameraComponent->GetComponentRotation();
    	
    	FHitResult HitBelowPlayer;
	for (USceneComponent* BodyPart : PointsToTrace)
	{
		GetWorld()->LineTraceSingleByObjectType(HitBelowPlayer, BodyPart->GetComponentLocation(),
			BodyPart->GetComponentLocation() + (FVector(0, 0, -1) * 250), ObjectsToLookFor);

		if (HitBelowPlayer.bBlockingHit)
		{
			if (GrabHitResult.GetActor() == HitBelowPlayer.GetActor())
			{
				return;
			}
		}
	}

	if (GrabHitResult.bBlockingHit &&GrabHitResult.Component->GetMass() <= 500.f)
	{
		if (GrabHitResult.GetActor()->IsA< AFG_LightAffectableActor>())
		{
			GrabbedLightAffectableObjectPtr = Cast< AFG_LightAffectableActor>(GrabHitResult.GetActor());
			GrabbedLightAffectableObjectPtr->bIsBeingGrabbed = true;
			GrabbedLightAffectableObjectPtr->MeshComp->SetRenderCustomDepth(true);
		}
		bIsPhysicsHandleActive = true;
		GrabHitResult.Actor->AddComponent(FName("GrabPointComp"), false, GetActorTransform(), UPhysicsHandleComponent::StaticClass());
		//GrabHitResult.Actor->AddComponent(FName("GrabPointComp"), false, GrabHitResult.GetActor()->GetActorTransform(), UPhysicsHandleComponent::StaticClass());
		PhysicsHandleComp->GrabComponentAtLocationWithRotation(GrabHitResult.GetComponent(), GrabHitResult.BoneName, GrabHitResult.Location, OtherItemRotation);
		PhysicsHandleComp->SetTargetLocation(PhysicsHandlePosition + FVector(200.f, 0.f, 0.f));

		UGameplayStatics::PlaySound2D(this, ActionBeginSFX);
		SFXObjHoldingComponent->SetSound(ObjectHoldingLoopSFX);
		SFXObjHoldingComponent->FadeIn(FadeTime, Volume, StartTime);
		bIsHoldingObject = true;
		GrabbedObject = GrabHitResult.GetActor();
		UStaticMeshComponent * StaticMeshPtr = Cast< UStaticMeshComponent>(GrabbedObject->GetComponentByClass(UStaticMeshComponent::StaticClass()));
		StaticMeshPtr->SetRenderCustomDepth(true);
	}
}

void AFG_PlayerCharacter::GrabObjectReleased()
{
	if (bIsPhysicsHandleActive && bIsHoldingObject)
	{
		if (GrabbedLightAffectableObjectPtr != nullptr)
		{
			GrabbedLightAffectableObjectPtr->bIsBeingGrabbed = false;
			GrabbedLightAffectableObjectPtr->MeshComp->SetRenderCustomDepth(false);
			GrabbedLightAffectableObjectPtr = nullptr;
			
		}

		if (PhysicsHandleComp)
		{
			PhysicsHandleComp->ReleaseComponent();
		}
		UGameplayStatics::PlaySound2D(this, ActionEndSFX);
		SFXObjHoldingComponent->FadeOut(FadeTime, 0.0f);
		bIsPhysicsHandleActive = false;
		bIsHoldingObject = false;
		UStaticMeshComponent * StaticMeshPtr = Cast< UStaticMeshComponent>(GrabbedObject->GetComponentByClass(UStaticMeshComponent::StaticClass()));
		StaticMeshPtr->SetRenderCustomDepth(false);

	}
}

void AFG_PlayerCharacter::CancelGrabbingObject()
{
	if (bIsHoldingObject)
	{
		FHitResult HitBelowPlayer;

		for (USceneComponent* BodyPart : PointsToTrace)
		{
			GetWorld()->LineTraceSingleByObjectType(HitBelowPlayer, BodyPart->GetComponentLocation(),
				BodyPart->GetComponentLocation() + (FVector(0, 0, -1) * GrabDistance), ObjectsToLookFor);

			if (HitBelowPlayer.bBlockingHit)
			{
				if (GrabbedObject == HitBelowPlayer.GetActor())
				{
					GrabObjectReleased();
					break;
				}
			}
		}
	}
}