박하의 나날

[25] 백팩(인벤토리), 아이템줍기

프로그래밍/Unreal

1.플레이어 백팩 선언

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once
 
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"
//class APickupItem;
UCLASS()
class BOOK_0528_API AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    // Sets default values for this character's properties
    AMyCharacter();
    TMap<FString, int> Backpack;
    TMap<FString, UTexture2D*> Icons;
 
    bool inventoryShowing;
    void Pickup(APickupItem *item);
};
cs

14)플레이어의 백팩을 위한 Map

15)아이템 아이콘들

17)백팩이 현재 보여지고 있는지 여부

18)아이템을 습득하도록 하는 멤버 함수

주석처리된 전방선언은 PickupItem 클래스를 만든 후 풀어주기.

project setting - input - action Mapping 으로 가서 i키를 누르면 inventory가 뜨게 입력해준다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "Book_0528.h"
#include "MyCharacter.h"
#include "PickupItem.h"
#include "MyHUD.h"
 
// Sets default values
AMyCharacter::AMyCharacter()
{
     inventoryShowing = false;
}
void AMyCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
    InputComponent->BindAction("Inventory", IE_Pressed, this&AMyCharacter::ToggleInventory);
}
void AMyCharacter::debug(int slot, FColor color, FString mess){
    if (GEngine){
        GEngine->AddOnScreenDebugMessage(slot, 5.f, color, mess);
    }
}
void AMyCharacter::ToggleInventory(){
    debug(0, FColor::Red, "inventory true");
}
 
cs

 

 

2.PickupItem 클래스 생성

부모클래스로 Actor를 선택해서 PickupItem클래스를 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#pragma once
 
#include "GameFramework/Actor.h"
#include "PickupItem.generated.h"
 
class AMyHUD;
 
UCLASS()
class BOOK_0528_API APickupItem : public AActor
{
    GENERATED_BODY()
        
public:    
    // Sets default values for this actor's properties
    APickupItem();    
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Item)
    FString Name;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Item)
    int32 Quantity;
 
    UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Item)
    USphereComponent* ProxSphere;
 
    UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Item)
    UStaticMeshComponent* Mesh;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Item)
    UTexture2D* Icon;
 
    UFUNCTION(BlueprintNativeEvent, Category = Collision)
    void Prox(AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, 
bool bFromSweep, const FHitResult & SweepResult);
 
    virtual void APickupItem::Prox_Implementation(AActor* OtherActor, 
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, 
const FHitResult & SweepResult);
    
};
cs

18)얻는 아이템 이름

21)아이템 개수

24)아이템을 줍는데 사용되는 충돌 구체

27)아이템 메시

30)UI/캔버스에서 보여질 아이템 아이콘

32~)proxSphere 안으로 무언가 들어오면 이 함수가 실행된다.

 

여기까지하면 이렇게.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
APickupItem::APickupItem()
{
    Name = "UNKNOWN ITEM";
    Quantity = 0;    
    ProxSphere = CreateDefaultSubobject<USphereComponent>(TEXT("ProxSphere"));
    Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
 
    RootComponent = Mesh;
    Mesh->SetSimulatePhysics(true);
 
    ProxSphere->OnComponentBeginOverlap.AddDynamic(this&APickupItem::Prox);
    ProxSphere->AttachTo(Mesh);
}
cs

3,4)이름, 갯수 초기화

5,6)언리얼 객체 초기화_상속받은 기본값을 가지고 있을 수는 있지만, 메시는 비어있는 채로 시작할 것.

나중에 블루프린트 내에서 실제 메시를 로드해야한다.

8,9)루트 객체를 메시로 만든다, 실제 물리를 시뮬레이트_습득 아이템들을 버리거나 이동했을때 튕겨지거나 굴러가게끔.

11)충돌 구체가 다른 액터랑 겹치면 APickupItem::Prox()가 실행되게 한다.

12)습득아이템의 ProxSphere 컴포넌트가 메시 루트 컴포넌트와 연결되었는지 확인한다.

레벨상에서 메시가 움직였을때 ProxSphere가 따라가게끔.

이 단계를 잊어버리면 메시가 움직여도 ProxSphere가 따라가지 않는다.

 

3.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void APickupItem::Prox_Implementation(AActor* OtherActor, UPrimitiveComponent* OtherComp, 
int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult)
{
    if (Cast<AMyCharacter>(OtherActor) == nullptr){
        return;
    }
    AMyCharacter *cha = Cast<AMyCharacter>(UGameplayStatics::GetPlayerPawn(GetWorld(), 0));
    APlayerController* pcon = GetWorld()->GetFirstPlayerController();
    AMyHUD* hud = Cast<AMyHUD>(pcon->GetHUD());
    hud->addMessage(Message(FString(TEXT("Picked up:")) + FString::FromInt(Quantity) + FString(TEXT("/")) 
+ Name, 5.f, FColor::White));
    cha->Pickup(this);
    Destroy();
}
 
cs

4)겹쳐진 액터가 플레이어가 아니면 그냥 종료.

7)플레이어에게 아이템을 주기위해 플레이어 아바타의 참조를 얻는다.

playerPawn객체는 실제로 AAvatar인스턴스이고 Cast<AAvatar>명령을 사용하여 AAvatar클래스로 형변환한다.

UGameStatics함수 집합은 전역함수라 어디서든지 접근할 수 있다.

8)컨트롤러의 참조를 얻는다.

9)컨트롤러에서 HUD의 참조를 얻는다. HUD는 실제로 플레이어의 컨트롤러에 붙어있어서 이렇게 접근한다.

12)플레이어가 아이템 줍줍. (원래 7~8 사이쯤에 위치해야되는데 옮기다보니까.. )

this 키워드를 사용한다. _this_픽업에 자기 자신을 참조하는 방법.

그리고 백팩에 아이템이 들어왔나 확인한다.

1
2
3
4
5
6
7
8
9
void AMyCharacter::Pickup(APickupItem *Item){
    if (Backpack.Find(Item->Name)){
        Backpack[Item->Name] += Item->Quantity;
    }
    else{
        Backpack.Add(Item->Name, Item->Quantity);
        Icons.Add(Item->Name, Item->Icon);
    }
}
cs

3)이미 백팩에 아이템이 있으므로 수량을 ++

7)없다면 추가

 

4.인벤토리 그리기_DrawTexture() 사용

아이콘의 종류, 현재 위치, 크기에 대한 정보를 저장하는 구조체를 선언해서 위젯에 모든 정보가 표시되도록.

위젯 구조체의 네개의 멤버 함수가 각각 위젯의 왼쪽,오른쪽, 위, 아래값을 얻는 함수이다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Icon{
    FString name;
    UTexture2D* tex;
    Icon(){ name = "UNKNOWN ICON"; tex = 0; }
    Icon(FString& iName, UTexture2D* iTex)
    {
        name = iName;
        tex = iTex;
    }
};
 
struct Widget{
    Icon icon;
    FVector2D pos, size;
    Widget(Icon iicon){
        icon = iicon;
    }
    float left(){ return pos.X;}
    float right(){ return pos.X + size.X; }
    float top(){ return pos.Y; }
    float bottom(){ return pos.Y + size.Y; }    
    }
}
cs

 

스크린에 위젯을 그리는 함수를 작성한다.

DrawWidgets() 함수 호출은 DrawHUD()함수에 추가되어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void AMyHUD::DrawWidgets(){
    for (int c = 0; c < widgets.Num(); c++)
    {
        DrawTexture(widgets[c].icon.tex, widgets[c].pos.X, widgets[c].pos.Y,
            widgets[c].size.X, widgets[c].size.Y, 0011);
        // draws relative to center.. don't want to use this version
        //DrawText( icons[c].name, pos, hudFont, FVector2D(.6f, .6f), FColor::Yellow );
        DrawText(widgets[c].icon.name, FLinearColor::Yellow,
            widgets[c].pos.X, widgets[c].pos.Y,
            hudFont, .6f, false);        
    }
}
 
void AMyHUD::DrawHUD(){
    Super::DrawHUD();
    dims.X = Canvas->SizeX;
    dims.Y = Canvas->SizeY;
    DrawWidgets();
}
cs

 116~)캔버스 변수 내에만 dims가 존재한다. addWidget()에서 사용하기 위해 업데이트.

 

I키를 눌렀을때 실행할 ToggleInventory()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void AMyCharacter::ToggleInventory(){
    APlayerController* pcon = GetWorld()->GetFirstPlayerController();
    AMyHUD* hud = Cast<AMyHUD>(pcon->GetHUD());
 
    if (!inventoryShowing){
        inventoryShowing = true;
        pcon->bShowMouseCursor = true;
        
        for (TMap<FString, int>::TIterator it = Backpack.CreateIterator(); it; ++it){
            debug(0, FColor::Red, "inventory true");
            FString fs = it->Key + FString::Printf(TEXT("x %d"), it->Value);
            UTexture2D* tex = NULL;
            if (Icons.Find(it->Key))
                tex = Icons[it->Key];
            Widget w(Icon(fs, tex));
            hud->addWidget(w);
        }
    }    
    else if(inventoryShowing == true){
        hud->clearWidgets();
        inventoryShowing = false;
        pcon->bShowMouseCursor = false;
        return;
    }
}
cs

2,3) 컨트롤러와 HUD를 얻는다.

11) coin x 5 같은 식으로 아이템의 이름과 개수를 조합

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void AMyHUD::addWidget(Widget widget){    
    FVector2D start(200200), pad(1212);
    widget.size = FVector2D(100100);
    widget.pos = start;
 
    for (int c = 0; c < widgets.Num(); c++){
        widget.pos.X += widget.size.X + pad.X;
        if (widget.pos.X + widget.size.X > dims.X){
            widget.pos.X = start.X;
            widget.pos.Y += widget.size.Y + pad.Y;
        }
    }
    widgets.Add(widget);
}
cs

2,3)격자를 기반으로 한 위젯의 위치를 찾아서 아이콘을 그린다.

6~)위치 계산

8~)오른쪽에 공간이 없다면 다음줄로 이동.

 

여기까지하면 이렇게.