352 lines
9.1 KiB
C++
352 lines
9.1 KiB
C++
// Copyright Team Lumi. All Rights Reserved.
|
|
|
|
|
|
#include "LumiCharacter.h"
|
|
|
|
#include "Engine/LocalPlayer.h"
|
|
#include "Engine/UserInterfaceSettings.h"
|
|
#include "Camera/CameraComponent.h"
|
|
#include "Components/CapsuleComponent.h"
|
|
#include "GameFramework/SpringArmComponent.h"
|
|
#include "GameFramework/Controller.h"
|
|
#include "InputActionValue.h"
|
|
#include "GameFramework/CharacterMovementComponent.h"
|
|
#include "WizardingCentral/Utils/OneDollar/UniStrokeDataTable.h"
|
|
#include "WizardingCentral/Utils/OneDollar/UniStrokeResult.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogLumiCharacter);
|
|
|
|
// Sets default values
|
|
ALumiCharacter::ALumiCharacter()
|
|
{
|
|
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
|
// PrimaryActorTick.bCanEverTick = true;
|
|
|
|
// Set size for collision capsule
|
|
GetCapsuleComponent()->InitCapsuleSize(35.0f, 90.0f);
|
|
|
|
// Allow double jumping
|
|
JumpMaxCount = 2;
|
|
|
|
// Don't rotate when the controller rotates. Let that just affect the camera.
|
|
bUseControllerRotationPitch = false;
|
|
bUseControllerRotationYaw = false;
|
|
bUseControllerRotationRoll = false;
|
|
|
|
// Create a camera boom (pulls in towards the player if there is a collision)
|
|
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
|
|
CameraBoom->SetupAttachment(RootComponent);
|
|
CameraBoom->TargetArmLength = 400.0f; // The camera follows at this distance behind the character
|
|
CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
|
|
CameraBoom->SocketOffset = DefaultBoomOffset;
|
|
|
|
// Create a follow camera
|
|
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
|
|
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
|
// Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
|
|
FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
|
|
|
|
// Spell System
|
|
bIsDrawing = false;
|
|
SpellRecognizer = new FUniStrokeRecognizer();
|
|
if (SpellTemplatesTable)
|
|
LoadSpellTemplates();
|
|
}
|
|
|
|
ELumiStance ALumiCharacter::GetCurrentStance() const
|
|
{
|
|
return CurrentStance;
|
|
}
|
|
|
|
// Called when the game starts or when spawned
|
|
void ALumiCharacter::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
APlayerController* LumiController = nullptr;
|
|
|
|
if (!GetPlayerController(LumiController))
|
|
{
|
|
UE_LOG(LogLumiCharacter, Error, TEXT("ALumiCharacter::BeginPlay() Failed to get LumiController!"));
|
|
return;
|
|
}
|
|
|
|
// Add Spell Overlay
|
|
if (SpellOverlayClass)
|
|
{
|
|
SpellOverlay = CreateWidget<USpellOverlay>(LumiController, SpellOverlayClass);
|
|
if (SpellOverlay)
|
|
{
|
|
SpellOverlay->AddToViewport();
|
|
SpellOverlay->SetVisibility(ESlateVisibility::Visible);
|
|
}
|
|
}
|
|
|
|
if (SpellTrainWidgetClass)
|
|
{
|
|
SpellTrainWidget = CreateWidget<UUserWidget>(LumiController, SpellTrainWidgetClass);
|
|
if (SpellTrainWidget)
|
|
{
|
|
SpellTrainWidget->AddToViewport();
|
|
SpellTrainWidget->SetVisibility(ESlateVisibility::Hidden);
|
|
}
|
|
}
|
|
|
|
bIsTraining = false;
|
|
SpellSystemState = Idle;
|
|
}
|
|
|
|
/*
|
|
// Called every frame
|
|
void ALumiCharacter::Tick(float DeltaTime)
|
|
{
|
|
Super::Tick(DeltaTime);
|
|
}
|
|
*/
|
|
|
|
bool ALumiCharacter::IsLastJump() const
|
|
{
|
|
return JumpCurrentCountPreJump == JumpMaxCount - 1;
|
|
}
|
|
|
|
void ALumiCharacter::OnGrabActionTriggered(const FInputActionValue& Value)
|
|
{
|
|
UE_LOG(LogLumiCharacter, Log, TEXT("Grab Triggered"));
|
|
}
|
|
|
|
void ALumiCharacter::OnJumpActionStarted(const FInputActionValue& Value)
|
|
{
|
|
// Broadcast that the jump has started
|
|
OnJumpStarted.Broadcast();
|
|
UE_LOG(LogLumiCharacter, Log, TEXT("Jump Started"));
|
|
|
|
Jump();
|
|
}
|
|
|
|
void ALumiCharacter::OnJumpActionOngoing(const FInputActionValue& Value) {}
|
|
|
|
void ALumiCharacter::OnJumpActionCompleted(const FInputActionValue& Value)
|
|
{
|
|
StopJumping();
|
|
}
|
|
|
|
void ALumiCharacter::OnMoveActionTriggered(const FInputActionValue& Value)
|
|
{
|
|
// input is a Vector2D
|
|
const FVector2D MovementVector = Value.Get<FVector2D>();
|
|
|
|
if (Controller == nullptr)
|
|
return;
|
|
|
|
// find out which way is forward
|
|
const FRotator Rotation = Controller->GetControlRotation();
|
|
const FRotator YawRotation(0, Rotation.Yaw, 0);
|
|
|
|
// get forward vector
|
|
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
|
|
|
// get right vector
|
|
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
|
|
|
|
// add movement
|
|
AddMovementInput(ForwardDirection, MovementVector.Y);
|
|
AddMovementInput(RightDirection, MovementVector.X);
|
|
}
|
|
|
|
void ALumiCharacter::OnLookActionTriggered(const FInputActionValue& Value)
|
|
{
|
|
if (SpellSystemState == Casting)
|
|
return;
|
|
|
|
// input is a Vector2D
|
|
const FVector2D LookAxisVector = Value.Get<FVector2D>();
|
|
|
|
if (Controller != nullptr)
|
|
{
|
|
// add yaw and pitch input to controller
|
|
AddControllerYawInput(LookAxisVector.X);
|
|
AddControllerPitchInput(LookAxisVector.Y);
|
|
}
|
|
}
|
|
|
|
void ALumiCharacter::OnDefaultStance_Implementation()
|
|
{
|
|
UE_LOG(LogLumiCharacter, Log, TEXT("OnDefaultStance_Implementation()"));
|
|
}
|
|
|
|
void ALumiCharacter::OnMagicStance_Implementation()
|
|
{
|
|
UE_LOG(LogLumiCharacter, Log, TEXT("OnMagicStance_Implementation()"));
|
|
}
|
|
|
|
void ALumiCharacter::OnSpellActionStarted()
|
|
{
|
|
if (SpellSystemState != Training)
|
|
SpellSystemState = Casting;
|
|
}
|
|
|
|
void ALumiCharacter::OnSpellActionCompleted()
|
|
{
|
|
if (SpellSystemState == Casting)
|
|
SpellSystemState = bIsTraining ? Training : Recognizing;
|
|
|
|
UE_LOG(LogLumiCharacter, Log, TEXT("OnSpellActionCompleted() Current State: %d"), SpellSystemState);
|
|
|
|
switch (SpellSystemState)
|
|
{
|
|
case Recognizing:
|
|
CastSpell();
|
|
break;
|
|
case Training:
|
|
ShowTrainWidget();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ALumiCharacter::OnSpellPositionActionTriggered(const FInputActionValue& Value)
|
|
{
|
|
if (SpellSystemState != Casting)
|
|
return;
|
|
|
|
APlayerController* LumiController = nullptr;
|
|
if (!GetPlayerController(LumiController))
|
|
return;
|
|
|
|
// Get Mouse Position
|
|
float MouseX = 0.0f, MouseY = 0.0f;
|
|
LumiController->GetMousePosition(MouseX, MouseY);
|
|
|
|
// Normalize Mouse Position
|
|
const FVector2D MousePosition = FVector2D(MouseX, MouseY);
|
|
const FVector2D ViewportSize = FVector2D(GEngine->GameViewport->Viewport->GetSizeXY());
|
|
const float ViewportScale = GetDefault<UUserInterfaceSettings>(
|
|
UUserInterfaceSettings::StaticClass()
|
|
)->GetDPIScaleBasedOnSize(FIntPoint(ViewportSize.X, ViewportSize.Y));
|
|
const FVector2D ScaledPosition = MousePosition / ViewportScale;
|
|
|
|
// Add Point to Spell Overlay
|
|
SpellOverlay->AddPoint(ScaledPosition);
|
|
}
|
|
|
|
void ALumiCharacter::SetIsTraining(const bool bTraining)
|
|
{
|
|
this->bIsTraining = bTraining;
|
|
|
|
// TODO: Remove Debug Message
|
|
if (bTraining)
|
|
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Black, "Training Mode On");
|
|
else
|
|
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Black, "Training Mode Off");
|
|
}
|
|
|
|
void ALumiCharacter::HideTrainWidget()
|
|
{
|
|
if (SpellTrainWidget == nullptr)
|
|
return;
|
|
|
|
SpellOverlay->Clear();
|
|
SpellTrainWidget->SetVisibility(ESlateVisibility::Hidden);
|
|
SpellSystemState = Idle;
|
|
}
|
|
|
|
void ALumiCharacter::ShowTrainWidget()
|
|
{
|
|
if (SpellTrainWidget == nullptr)
|
|
return;
|
|
|
|
SpellTrainWidget->SetVisibility(ESlateVisibility::Visible);
|
|
}
|
|
|
|
void ALumiCharacter::AddSpellTemplateToTable(const FString& Name)
|
|
{
|
|
FUniStrokeDataTable Entry;
|
|
|
|
Entry.Name = Name;
|
|
Entry.Points = *SpellOverlay->GetSpellPoints();
|
|
|
|
const FString ContextString = "Spell Templates";
|
|
TArray<FUniStrokeDataTable*> Rows;
|
|
SpellTemplatesTable->GetAllRows<FUniStrokeDataTable>(ContextString, Rows);
|
|
SpellTemplatesTable->AddRow(FName(*FString::FromInt(Rows.Num() + 1)), Entry);
|
|
|
|
SpellRecognizer->AddTemplate(Name, Entry.Points);
|
|
|
|
HideTrainWidget();
|
|
}
|
|
|
|
bool ALumiCharacter::GetPlayerController(APlayerController*& LumiController) const
|
|
{
|
|
LumiController = Cast<APlayerController>(this->Controller);
|
|
|
|
return LumiController != nullptr;
|
|
}
|
|
|
|
void ALumiCharacter::LoadSpellTemplates() const
|
|
{
|
|
if (SpellTemplatesTable == nullptr)
|
|
return;
|
|
|
|
const FString ContextString = "Spell Templates";
|
|
TArray<FUniStrokeDataTable*> Rows;
|
|
|
|
SpellTemplatesTable->GetAllRows<FUniStrokeDataTable>(ContextString, Rows);
|
|
|
|
for (const FUniStrokeDataTable* const T : Rows)
|
|
SpellRecognizer->AddTemplate(T->Name, T->Points);
|
|
}
|
|
|
|
void ALumiCharacter::CastSpell()
|
|
{
|
|
const TArray<FVector2D>* CurrentPoints = SpellOverlay->GetSpellPoints();
|
|
const FUniStrokeResult Result = SpellRecognizer->Recognize(*CurrentPoints, false);
|
|
|
|
bool bHasMatch = false;
|
|
if (Result.Score < 0.8f)
|
|
{
|
|
UE_LOG(LogLumiCharacter, Log, TEXT("ALumiCharacter::CastSpell() No match found!"));
|
|
}
|
|
else
|
|
{
|
|
bHasMatch = true;
|
|
UE_LOG(LogLumiCharacter, Log, TEXT("ALumiCharacter::CastSpell() Spell: %s"), *Result.Name);
|
|
}
|
|
|
|
// TODO: Handle Spell Casting
|
|
|
|
// Clean up
|
|
SpellOverlay->Clear();
|
|
SpellSystemState = Idle;
|
|
}
|
|
|
|
ELumiStance ALumiCharacter::SetCurrentStance(const ELumiStance NewStance)
|
|
{
|
|
const ELumiStance OldStance = CurrentStance;
|
|
|
|
UCharacterMovementComponent* Movement = GetCharacterMovement();
|
|
|
|
if (NewStance == ELumiStance::Default)
|
|
{
|
|
Movement->bOrientRotationToMovement = true;
|
|
Movement->bUseControllerDesiredRotation = false;
|
|
|
|
OnDefaultStance();
|
|
}
|
|
else if (NewStance == ELumiStance::Magic)
|
|
{
|
|
Movement->bOrientRotationToMovement = false;
|
|
Movement->bUseControllerDesiredRotation = true;
|
|
|
|
OnMagicStance();
|
|
}
|
|
|
|
CurrentStance = NewStance;
|
|
return OldStance;
|
|
}
|
|
|
|
void ALumiCharacter::OnSpellTrainSwitchActionCompleted()
|
|
{
|
|
SetIsTraining(!this->bIsTraining);
|
|
}
|