
① ActionKey("Click")를 누르면 바인딩 된 Click 함수가 호출된다.
void ALvPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAction<ALvPlayer>(TEXT("Click"), EInputEvent::IE_Pressed, this, &ALvPlayer::Click);
}
void ALvPlayer::Click()
{
ALvPlayerController* PlayerController = Cast<ALvPlayerController>(GetController());
if (IsValid(PlayerController))
{
if (GetState() == EPlayerState::Aim)
PlayerController->UseTool();
else
PlayerController->Click();
}
}
② ALvPlayerController의 Click 함수가 호출된다.
마우스가 클릭한 액터가 낚시터고, 플레이어가 낚시 가능 상태면 Fishing 함수가 호출된다.
void ALvPlayerController::Click()
{
FHitResult result;
bool Hit = GetHitResultUnderCursor(ECollisionChannel::ECC_GameTraceChannel3, false, result);
if (Hit)
{
AFishingSpot* Spot = Cast<AFishingSpot>(result.GetActor());
if (IsValid(Spot))
{
ALvPlayer* PlayerCharacter = Cast<ALvPlayer>(GetCharacter());
if (IsValid(PlayerCharacter))
{
if (PlayerCharacter->GetCanFishing() == true)
PlayerCharacter->Fishing();
}
}
}
}
③ 플레이어의 앞쪽 특정 범위 내에 낚시터가 있으면 SetState 함수가 호출된다.
void ALvPlayer::Fishing()
{
TArray<FHitResult> CollisionResult;
FVector StartLocation = GetActorLocation() +
GetActorForwardVector() * 30.f;
FVector EndLocation = StartLocation +
GetActorForwardVector() * 500.f;
FCollisionQueryParams param(NAME_None, false, this);
bool Hit = GetWorld()->SweepMultiByChannel(
CollisionResult, StartLocation,
EndLocation, FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel2,
FCollisionShape::MakeSphere(50.f),
param);
if (Hit)
{
SetState(EPlayerState::Fishing);
mFinishFishing = false;
}
}
④ ServerSetState 함수가 호출된다.
void ALvPlayer::SetState(EPlayerState State)
{
if (mPlayerState != State)
{
mPlayerState = State;
ServerSetState(mPlayerState);
}
}
⑤ ServerSetFishingTimer 함수가 호출된다.
void ALvPlayer::ServerSetState_Implementation(EPlayerState State)
{
if (State == EPlayerState::Fishing)
{
ServerSetFishingTimer();
}
else
{
if (FishingTimerHandle.IsValid())
{
GetWorldTimerManager().ClearTimer(FishingTimerHandle);
FishingTimerHandle.Invalidate();
ServerSetBanFishingTimer();
}
}
}
⑥ Timer가 3 ~ 5초 사이 랜덤하게 세팅된다.
void ALvPlayer::ServerSetFishingTimer()
{
if (FishingTimerHandle.IsValid() == false)
{
mFishingTime = FMath::RandRange(3, 5);
GetWorldTimerManager().SetTimer(FishingTimerHandle, FTimerDelegate::CreateUObject(this, &ALvPlayer::ServerOnFishingTimerExpired), mFishingTime, false);
}
}
⑦ mFishingTime가 끝나면 ServerOnFishingTimerExpired 함수가 호출된다.
ClientNotifyPressE 함수가 호출되면 E키를 입력할 수 있게 된다.
void ALvPlayer::ServerOnFishingTimerExpired()
{
ClientNotifyPressE();
float Time = 1.f;
GetWorldTimerManager().SetTimer(PendingClientResponseTimerHandle, FTimerDelegate::CreateUObject(this, &ALvPlayer::ServerOnPendingClientResponseTimerExpired), Time, false);
FishingTimerHandle.Invalidate();
}
void ALvPlayer::ClientNotifyPressE_Implementation()
{
mCanEKeyPressed = true;
}
(Case 1)
E키를 누르지 않은 경우
⑧ ServerOnPendingClientResponseTimerExpired 함수가 호출된다.
Timer가 설정되고, 1초 후에 Idle 상태로 돌아간다.
E를 누르지 못하는 상태가 되고, 낚시는 종료된다.
void ALvPlayer::ServerOnPendingClientResponseTimerExpired()
{
GetWorldTimerManager().SetTimer(SetIdleStateTimerHandle, FTimerDelegate::CreateLambda(
[this]()
{
if (IsValid(this))
{
this->SetState(EPlayerState::Idle);
SetIdleStateTimerHandle.Invalidate();
}
}), 1.0f, false);
ClientOnFishingFinished();
}
void ALvPlayer::ClientOnFishingFinished_Implementation()
{
mFinishFishing = true;
mCanEKeyPressed = false;
}
(Case 2)
낚시 도중 움직인 경우
⑧ Idle 상태로 변경되고, ServerSetBanFishingTimer 함수가 호출된다.
void ALvPlayer::SetState(EPlayerState State)
{
if (mPlayerState != State)
{
mPlayerState = State;
ServerSetState(mPlayerState);
}
}
void ALvPlayer::ServerSetState_Implementation(EPlayerState State)
{
if (State == EPlayerState::Fishing)
{
ServerSetFishingTimer();
}
else
{
if (FishingTimerHandle.IsValid())
{
GetWorldTimerManager().ClearTimer(FishingTimerHandle);
FishingTimerHandle.Invalidate();
ServerSetBanFishingTimer();
}
}
}
⑨ 5초동안 낚시를 하지 못하게 된다.
void ALvPlayer::ServerSetBanFishingTimer()
{
mBanFishingTime = 5.f;
mCanFishing = false;
GetWorldTimerManager().SetTimer(FishingBanTimerHandle, FTimerDelegate::CreateUObject(this, &ALvPlayer::ServerOnBanFishingTimerExpired), mBanFishingTime, false);
}
⑧ E키를 누르면 CheckSuccessedFishing 함수가 호출된다.
void ALvPlayer::CheckSuccessedFishing()
{
if (true == mCanEKeyPressed)
{
ServerEKeyPressed();
mCanEKeyPressed = false;
}
}
⑨ ServerEKeyPressed 함수가 호출된다. ⑦에서 1초로 세팅한 타이머가 유효하면 InventoryComponent에 아이템을 추가한다.
void ALvPlayer::ServerEKeyPressed_Implementation()
{
if (PendingClientResponseTimerHandle.IsValid())
{
int ItemID = FMath::RandRange(1, 17);
ClientOnFishingFinished();
GetInventoryComponent()->ServerAddItem(ItemID);
GetWorldTimerManager().SetTimer(SetIdleStateTimerHandle, FTimerDelegate::CreateLambda(
[this]()
{
if (IsValid(this))
{
this->SetState(EPlayerState::Idle);
SetIdleStateTimerHandle.Invalidate();
}
}), 1, false);
GetWorldTimerManager().ClearTimer(PendingClientResponseTimerHandle);
PendingClientResponseTimerHandle.Invalidate();
}
}
⑩ ServerAddItem 함수가 호출된다.
void UInventoryComponent::ServerAddItem_Implementation(int32 ItemID)
{
mItems.Add(ItemID);
}
⑪ mItems이 바뀌면서 OnRep_Items 함수가 호출된다.
// InventoryComponent.h
private:
UPROPERTY(ReplicatedUsing = OnRep_Items)
TArray<int32> mItems;
// InventoryComponent.cpp
void UInventoryComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UInventoryComponent, mItems);
DOREPLIFETIME(UInventoryComponent, mMK);
}
void UInventoryComponent::OnRep_Items()
{
OnItemsChangedEvent.Broadcast(mItems);
}
⑫ 이벤트 발생에 따라 InventoryBase에 바인딩된 함수(OnItemsChanged)가 호출된다.
void UInventoryBase::NativeTick(const FGeometry& _geo, float _DeltaTime)
{
Super::NativeTick(_geo, _DeltaTime);
APlayerController* Controller = GetOwningPlayer();
ALvPlayer* Character = Cast<ALvPlayer>(Controller->GetCharacter());
UInventoryComponent* Component = Character->GetInventoryComponent();
if (OnceCheck == false)
{
Component->OnItemsChangedEvent.AddUObject(this, &UInventoryBase::OnItemsChanged);
OnItemsChanged(Component->GetItems());
OnceCheck = true;
}
}
void UInventoryBase::OnItemsChanged(TArray<int32> Items)
{
mTileView->ClearListItems();
for (int32 Item : Items)
{
FItemTable* Table = UInventory::GetInst(GetGameInstance())->GetInfoItem(Item);
UItemDataBase* pNewData = NewObject<UItemDataBase>();
pNewData->SetItemIconPath(Table->TexturePath);
pNewData->SetItemID(Item);
mTileView->AddItem(pNewData);
}
}