[UE5] Longvinter 모작 - 낚시 구현

·2023년 3월 23일

UE5

목록 보기
5/5
post-thumbnail

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

0개의 댓글