Inventory System

김민오·2022년 9월 17일
0

인벤토리의 전반적인 구조는 아래의 위키 사이트를 참고하여 구현하였다.
https://nerivec.github.io/old-ue4-wiki/pages/c-inventory.html

다만 위 사이트에서 구현된 인벤토리 시스템을 내 프로젝트에 그대로 적용하기에는 몇 가지 수정해야할점들이 있었고, 개인적으로 봤을때 더 좋은 방법이 생각나서 그와 관련된 내용들을 이 글에 정리해볼까 한다.

참고한 사이트에 올라와있는 인벤토리 시스템의 구조이다 ManPickup의 경우 플레이어가 아이템 주변으로 다가가서 특정키를 눌러 수집할 수 있는류의 아이템이고 AutoPickup의 경우 플레이어가 에어리어 안으로 들어가기만하면 자동으로 수집되는 아이템이다.

AutoPickup의 경우 이미 내 프로젝트에 구현되어있었기 때문에 특별히 다루지는 않았다.

내가 구현해보고 싶었던건 수동으로 Pickup한 아이템을 저장하여 이를 UI로 확인할 수 있는 멀티플레이 인벤토리 시스템이였기 때문이다.

일단 저 다이어그램에서 드러나지않은 부분이 있는데, 사이트에서는 인벤토리 기능 자체를 플레이어 컨트롤러에 구현을 해놨다. 그러나 나는 인벤토리라는 기능 자체를 아예 컴포넌트로 만들어서 탈부착하기 쉽게 만들면 어떨까 싶어 액터 컴포넌트를 기반으로 구현을 진행했다.

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SCIFICOMBAT_API UInventoryComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UInventoryComponent();



	UFUNCTION(BlueprintCallable, Category = "Utils")
	int32 GetInventoryWeight();

	UFUNCTION(BlueprintCallable, Category = "Utils")
	bool AddItemToInventoryByID(FName ID);

	/** Function to check for the closest Interactable in sight and in range. */
	void CheckForInteractables();

	// 멀티플레이를 위해서 사이트에서 제공되는 Interaction 함수를 확장했다.
	void Interaction();
	void InteractPickupItem();
	UFUNCTION(Server, Reliable)
	void ServerInteractPickup();
	UFUNCTION(NetMulticast, Reliable)
	void NetMulticastInteractPickup();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
	//void Interact();
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
public:
	
    // 플레이어가 어떤 아이템을 현재 interaction 할 수 있는지
    // 그리고 플레이어의 인벤토리에는 현재 어떤 아이템들이 있는지
    // 이런 아이템들은 네트워크상으로 공유되어야 한다고 생각했기때문에 두 멤버는 Replicated 옵션을 적용했다.
	UPROPERTY(Replicated, VisibleAnywhere, BlueprintReadWrite)
	class AInteractableBase* current_interactable;
	UPROPERTY(Replicated, VisibleAnywhere, BlueprintReadWrite)
	TArray<FInventoryItem> inventory;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
	int32 money;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	int32 inventory_slot_limit;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	int32 inventory_weight_limit;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	int32 interact_range = 1500;

	UPROPERTY()
	class ACombatCharacter* inventory_owner;

	UFUNCTION()
	void SetInventoryOwer(class ACombatCharacter* _owner);
};
void UInventoryComponent::Interaction()
{
	// Ineraction과 current_interactable에 네트워크가 적용되지 않는다면
    // 다른 유저가 습득한 아이템이, 다른 유저의 화면에서는 업데이트가 되지 않는다던지 하는...
    // 그런 문제가 생길 수 있다.
	if (current_interactable)
	{
		UE_LOG(LogTemp, Warning, TEXT("Get Item"));
		current_interactable->Interact(this);
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("Not Valid Getting Item"));
	}
}

void UInventoryComponent::InteractPickupItem()
{
	ServerInteractPickup();
}

void UInventoryComponent::ServerInteractPickup_Implementation()
{
	NetMulticastInteractPickup();
}
void UInventoryComponent::NetMulticastInteractPickup_Implementation()
{
	Interaction();
}

액터 컴포넌트 구현 부분은 전부 올리기에는 길어질것같아 Interaction 부분만 올렸다.

위의 사이트에서는 캐릭터의 주변에 아이템과 상호작용하기위한 SphereCollision Component를 부착했는데, 내 프로젝트에서는 아이템에 상호작용하기위한 Collision을 부착했기 때문에 아이템 역시 동일한 방식으로 구현하는게 좋다고 생각하여 같은 방법으로 구현했다.

이 부분은 크게 어렵지않았다.

AInteractableBase::AInteractableBase()
{

	// 다른 부분은 생략 . . . . .
    
    // 콜리전 초기화 부분
	item_collection_sphere = CreateDefaultSubobject<USphereComponent>(TEXT("ItemCollectionSphere"));
	item_collection_sphere->SetupAttachment(RootComponent);
	item_collection_sphere->SetSphereRadius(200.f);

	// 다른 부분은 생략 . . . . .
}
void AInteractableBase::BeginPlay()
{
	Super::BeginPlay();
	

	if (HasAuthority())
	{
    // 오버랩 시작, 끝 부분 Dynamic Delegates
		item_collection_sphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
		item_collection_sphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
		item_collection_sphere->OnComponentBeginOverlap.AddDynamic(this, &AInteractableBase::OnSphereOverlap);
		item_collection_sphere->OnComponentEndOverlap.AddDynamic(this, &AInteractableBase::OnSphereEndOverlap);
	}

	if (pickup_widget)
	{
		pickup_widget->SetVisibility(false);
	}
}
void AInteractableBase::OnSphereOverlap(
	UPrimitiveComponent* OverlappedComponent,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex,
	bool isFromSweep,
	const FHitResult& SweepResult
)
{
	ACombatCharacter* combat_character = Cast<ACombatCharacter>(OtherActor);
	if (combat_character)
	{
    	//  캐릭터와 overrlap 되면
		if (pickup_widget)
		{
			pickup_widget->SetVisibility(true);
		}
        // 현재 상호작용한 아이템으로 적용
		combat_character->inventory_component->current_interactable = this;
	}
}


void AInteractableBase::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	ACombatCharacter* combat_character = Cast<ACombatCharacter>(OtherActor);
	if (combat_character)
	{
    	// 캐릭터와 overralp이 끝나게 되면
		if (pickup_widget)
		{
			pickup_widget->SetVisibility(false);
		}
        // 해제
		combat_character->inventory_component->current_interactable = nullptr;
	}
}
profile
https://github.com/gimhema, https://gimhema.tistory.com/

0개의 댓글