What I have learned
- Handling Timeline in C++
- Create an AI in C++ that move when you don’t watch it
- Discover and use the Enhanced input
- Create a blink eyes system
- Create an Echolocation system
Enhanced input
It’s a plugin that’s allow you to have more complex input. To enable it, you have to go in the plugin menu and search for “Enhanced Input”. I have enabled it for 2 reasons:
- To test it and understand it how it works
- To bind the echolocation system with blink system in one input
He works with an Input Mapping Context, he describes the rules for triggering the different Inputs and link with an Input Action. An Input Action is the communication between the code and the Input Mapping context. In my project have 5 Inputs Actions:
- Input Action Blink ⇒ Action to trigger the blink action
- Input Action Camera ⇒ Inputs to move the camera with the mouse
- Input Action Movements ⇒ inputs to move the player
- Input Action Close Eye ⇒ Action to trigger the eye closed and keep it
- Input Action Open Eye ⇒ Action to trigger the eye open.
The first thing to do is add an input context to a player controller
void APlayerChararcter::BeginPlay()
{
Super::BeginPlay();
if (APlayerController* PlayerController = Cast(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<
UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(MappingContext, 0);
}
}
}
void APlayerChararcter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* PlayerEnhancedInputComponent = Cast(PlayerInputComponent))
{
PlayerEnhancedInputComponent->BindAction(MovementAction, ETriggerEvent::Triggered, this,
&APlayerChararcter::MovePlayer);
PlayerEnhancedInputComponent->BindAction(CameraAction, ETriggerEvent::Triggered, this,
&APlayerChararcter::MoveCamera);
PlayerEnhancedInputComponent->BindAction(HoldEyeAction, ETriggerEvent::Triggered, this,
&APlayerChararcter::ClosedEyeAction);
PlayerEnhancedInputComponent->BindAction(ReleaseEyeAction, ETriggerEvent::Triggered, this,
&APlayerChararcter::OpenEyeAction);
PlayerEnhancedInputComponent->BindAction(BlinkAction, ETriggerEvent::Completed, this,
&APlayerChararcter::Blink);
}
}
void APlayerChararcter::MovePlayer(const FInputActionValue& Instance)
{
if (!bIsHold)
{
const FVector AxisValue = Instance.Get();
AddMovementInput(GetActorForwardVector() * AxisValue.X);
AddMovementInput(GetActorRightVector() * AxisValue.Y);
}
}
void APlayerChararcter::MoveCamera(const FInputActionValue& Instance)
{
const FVector2D AxisValue = Instance.Get();
AddControllerPitchInput(AxisValue.Y * -1);
AddControllerYawInput(AxisValue.X);
}
Eye blink and blood effect
It’s the main mechanic of the prototype, every X seconds, the character must blink, and the antagonist is going to move towards you. He works with 2 timelines who manage the visual effect and 1 timer to trigger every thing with a rate of 1 second. At the end of the timeline, “Blink”, an Event is executed to unpause the timer and restart the cycle. The blood eye effect is used to show how soon the character is going to blink.
void APlayerChararcter::Blink()
{
if (!bIsSpaceBarPressed)
{
bIsSpaceBarPressed = true;
TimelineBlink->PlayFromStart();
TimelineBloodEye->Reverse();
GetWorldTimerManager().PauseTimer(TimerHandleCheckBlink);
HowManyTimeBeforeBlink = TimeBeforeBlinkMax;
}
}
Echolocation
This mechanic is a way to help the player to find any important object who implemented the echolocation system. He holds the space bar button, and then the character focused to find where any important object are revealed. However, The antagonist will still be moving towards you. To find every actor who need the echolocation system , I have created an Interface who enable/disable the visibility of the effect.
void APlayerChararcter::ClosedEyeAction(const FInputActionValue& Instance)
{
if (!TimelineBlink->IsPlaying())
{
GetWorldTimerManager().PauseTimer(TimerHandleCheckBlink);
HowManyTimeBeforeBlink = TimeBeforeBlinkMax;
bIsBlink = true;
bIsHold = true;
TimelineBlink->PlayFromStart();
DynamicMaterialBloodEyeEffect->SetScalarParameterValue("Lerp", 0);
TimelineBloodEye->Stop();
}
}
void APlayerChararcter::OpenEyeAction(const FInputActionValue& Instance)
{
if (!TimelineBlink->IsPlaying() && bIsHold)
{
GetWorldTimerManager().UnPauseTimer(TimerHandleCheckBlink);
bIsBlink = false;
bIsHold = false;
TimelineBlink->PlayFromStart();
TimelineBloodEye->PlayFromStart();
}
}
UINTERFACE(MinimalAPI)
class UIEcholocation : public UInterface
{
GENERATED_BODY()
};
/**
*
*/
class DOLLS_API IIEcholocation
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
UFUNCTION(BlueprintCallable,BlueprintImplementableEvent)
void EnableVisibility(bool bIsSet);
};
A.I Doll
It’s the antagonist and the only enemy. She will follow you and try to get closer to kill you. She is only moving if you don’t watch her. So if you blink, you close your eyes, or you don’t look at her, she is going to move. I have created a service that check if she is render and if it’s the case, I check if the character got eyes closed.
UServiceCheckIsRendering::UServiceCheckIsRendering()
{
NodeName = TEXT("Is Actor rendering");
}
void UServiceCheckIsRendering::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
OwnerComp.GetBlackboardComponent()->SetValueAsBool(GetSelectedBlackboardKey(), CheckIfIsRender(OwnerComp));
}
bool UServiceCheckIsRendering::CheckIfIsRender(UBehaviorTreeComponent& OwnerComp) const
{
const AAIController* Controller = OwnerComp.GetAIOwner();
const APlayerChararcter* Player = Cast(UGameplayStatics::GetPlayerPawn(GetWorld(),0));
bool bIsRender = Controller->GetPawn()->WasRecentlyRendered(0.1f);
if(bIsRender)
{
bIsRender = !Player->GetIsCloseEyes();
}
UE_LOG(LogTemp, Warning, TEXT("%d"),bIsRender);
OwnerComp.GetBlackboardComponent()->SetValueAsBool(GetSelectedBlackboardKey(), bIsRender);
return bIsRender;
}