ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 블루아카이브 루트슈터 팬게임 프로젝트(12) - 방어구 스탯 적용
    루트슈터 프로젝트 2024. 1. 10. 23:58

    안녕하세요. 어제는 방어구 아이템을 새로 생성하고 인벤토리로 주워서 툴팁 옵션을 볼 수 있게 했는데요. 이번에는 그 방어구를 장착시키고 스탯을 실제로 플레이어에게 적용 시킬 수 있게 하겠습니다. 

     

     

    PlayerCharacter.h

    	UPROPERTY(EditDefaultsOnly)
    	FItemData armorSlot[3]; //캐릭터 장착중인 방어구들 
    
    	float playerStat[7] = {0, }; // 방어구로부터 얻은 플레이어 스탯
    	void InitPlayerStat(); //방어구를 읽어들여서 위의 playerStat에 적용

     

    플레이어 캐릭터의 헤더파일에 장착된 아이템에 대한 정보를 가진 배열과 방어구의 스탯을 전부 합산해서 담을 실수형 배열, 그리고 방어구들을 읽어들여서 실수형 배열에 담는 역할을 하는 함수를 선언해줍니다. 

     

     

    EquipArmorSlot.h

    UCLASS()
    class PROJECTBLUELANDS_API UEquipArmorSlotUI : public UUCustomUI
    {
    	GENERATED_BODY()
    
    public:
    	void Init() override;
    	void SetTexture(UTexture2D* tex);
    	void Refresh();
    
    protected:
    	FReply NativeOnMouseButtonDown(const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) override; //슬롯이 마우스 클릭받았을때 이벤트 처리
    	void NativeOnDragDetected(const FGeometry & InGeometry, const FPointerEvent & InMouseEvent, UDragDropOperation *& OutOperation) override;
    	bool NativeOnDrop(const FGeometry & InGeometry, const FDragDropEvent & InDragDropEvent, UDragDropOperation * InOperation) override;
    	virtual void NativeConstruct() override;
    
    public:
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMax = 4, UIMin = -1))
    	int armorSlotNum; //플레이어의 방어구 장착슬롯 배열의 몇번째 num과 대응되는가에 대한 변수
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
    	UImage* icon; //아이템의 이미지
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
    	UImage* backColor; //아이템의 등급에 따른 뒷배경색깔
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
    	EItemType slotItemType; //슬롯에 들어있는 아이템 타입
    
    	UPROPERTY(BlueprintReadOnly)
    	ESlot slotType = ESlot::ES_ARMOREQ;
    
    	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    	TSubclassOf<UEquipArmorSlotUI> DragVisualClass; //마우스 드래그할때 보여지는 슬롯
    
    
    private:
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    	TSubclassOf<UArmorTooltipWidget> ArmorToolTipClass;
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    	TObjectPtr<UArmorTooltipWidget> ArmorToolTipWidget;
    
    	void SetArmorToolTipWidget();
    };

     

    그리고 방어구 장착 슬롯 위젯을 따로 생성해줬습니다. 솔직히 인벤토리 슬롯과 구조가 매우 흡사합니다. 지금 생각해보니 인벤토리의 슬롯들의 상위 클래스를 하나 만들어 주는 것이 좋을 것 같은데.. 너무 경솔하게 프로그래밍을 한 것 같습니다. cpp는 인벤토리 슬롯과 매우 구조가 흡사하므로 생략하겠습니다. 

     

     

    그리고 인벤토리 위젯에 방어구 장비칸을 3개 만들어 줍니다. 제 게임에서 방어구는 3개만 착용할 수 있습니다. 저 허전한 빈공간은 나중에 아이템 종류를 하나 더 늘린다면 넣어야 겠네요. 일단은 방어구까지 구현해서 완성하는것을 제 1차 목표로 해야겠습니다. 

     

     

    PlayerCharacter.cpp

    void APlayerCharacter::InitPlayerStat() //방어구로 얻는 플레이어 스탯 초기화
    {
    	for(int t = 0; t<7; t++) //플레이어 스탯 초기화
    	{
    		playerStat[t] = 0.0;
    	}
    
    	for(int i = 0; i<3; i++)
    	{
    		if(armorSlot[i].itemType == EItemType::EIT_DEFAULT) continue; //방어구 칸이 비어있는 경우 패스
    		switch (armorSlot[i].armorStat.armorType)
    		{
    		case EArmorType::EAT_HAT:
    			playerStat[1] += armorSlot[i].armorStat.mainStat;
    			break;
    		case EArmorType::EAT_WATCH:
    			playerStat[5] += armorSlot[i].armorStat.mainStat;
    			break;
    		case EArmorType::EAT_BACKPACK:
    			playerStat[0] += armorSlot[i].armorStat.mainStat;
    			break;
    		default:
    			break;
    		}
    		for(int t = 1; t<7; t++)
    		{
    			playerStat[t] += armorSlot[i].armorStat.option[t];
    		}
    	}
    }

     

    장착한 방어구들의 스탯을 읽어오는 함수입니다. 일단 모두 0으로 초기화 해준 후 방어구 하나하나 읽어들이는 방식입니다. 코드 자체가 이해 안되는 부분은 없습니다만, 이 옵션들은 번호마다 특정 옵션을 상징합니다. 예를들어 1번 옵션은 무기데미지 증가이기 때문에 무기데미지 증가 옵션은 전부 1번 인덱스에 더해주면 되는 방식입니다.

     

     

     

    void AGun::SpawnBullet(FVector direction)
    {
    	APlayerCharacter* player = Cast<APlayerCharacter>(GetOwner());
    	AProjectile* proj = (GetWorld()->SpawnActor<AProjectile>(
    		projectileClass ,
    		projectileSpawnPoint->GetComponentLocation(), 
    		projectileSpawnPoint->GetComponentRotation()
    	));
    	if(proj && player)
    	{
    		float bulletDamage = gunStat.weaponDamage * (1.0 + player->playerStat[1]/100.0); //플레이어의 총기 데미지 스탯도 가산해줌
    		if(FMath::RandRange(0.0, 100.0) < this->gunStat.critChance + player->playerStat[5]) //크리티컬 성공시
    		{
    			proj->SetDamage(bulletDamage * ((this->gunStat.critDamage + player->playerStat[6]) / 100.0)); //투사체 스폰 및 데미지 설정 
    			proj->bIsCritical = true;
    		}
    		else{ 
    			proj->SetDamage(bulletDamage); //투사체 스폰 및 데미지 설정 
    			proj->bIsCritical = false;
    		}
    		proj->SetOwner(this); //이 총알의 주인 = 총
    		proj->SetVelocity(direction);
    	}
    }

     

    그리고 총에 대한 클래스에서 플레이어의 스탯까지 합산하도록 합니다. 여기서는 무기데미지와 치명타 확률, 치명타 데미지가 반영됩니다. 

     

     

     

    void APlayerCharacter::PullTrigger() //방아쇠 당기기
    {
    	if(isOpenInv || bNowReloading) return;
    	GetWorldTimerManager().SetTimer(FireRateTimerHandle, this, &APlayerCharacter::Fire, gun->gunStat.fireRate * (1.0 - playerStat[2] / 100.0), true, 0.0f);
    }

     

    이곳은 플레이어캐릭터의 클래스입니다. 여기에서 총의 발사속도가 반영됩니다.

     

     

    PlayerCharacter.cpp

    void APlayerCharacter::StartReload()
    {
    	if( gun->nowMag == gun->gunStat.maxMag) return;
    
    	reloadSpeed = gun->gunStat.reloadTime * (1.0 - (playerStat[3] / 100.0));
    	if (FDele_StartReload.IsBound())
    	{
    		GetWorldTimerManager().ClearAllTimersForObject(this);
    		bNowReloading = true;
    		FDele_StartReload.Broadcast();
    	}
    
    }

     

    그리고 마지막으로 재장전 속도입니다. 재장전 속도는 플레이어의 재장전 애니메이션의 속도와 연관이 있습니다. 그러므로 재장전을 시작하는 함수에서 재장전에 총 걸리는 시간이 몇초인지 계산합니다. 

     

     

     

     

    PlayerAnimInstance.cpp

    void UPlayerAnimInstance::StartReloading()
    {
        if(player) reloadAnimPlaySpeed = 2.17 / player->reloadSpeed;
    
        bIsReload = true;
    }

     

    그리고 플레이어의 애니메이션을 담당하는 클래스에서 재장전 애니메이션의 배속을 어느정도로 할지 정합니다. 2.17초는 제가 사용하는 재장전 애니메이션의 1배속일때 걸리는 시간입니다. 만약에 재장전에 걸리는 시간을 1초로 하고 싶다면 2.17/1 = 대략 2.17배로 재장전 애니메이션 배속을 돌려버리면 되겠죠.

     

     

     

    이렇게 재장전 애니메이션에 Play Rate에 방금 c++에서 정한 reloadAnimPlaySpeed 변수를 바인딩 하면 됩니다. 

     

     

     

    오늘은 이만 줄이겠습니다. 뭔가 엄청나게 날림으로 설명을 진행한 느낌이 드는데.. 점점 규모가 커지니 저도 감당이 잘 안되는거 같습니다. 이제 정말로 점점 게임에 가까워 지고 있습니다만 뭔가 가까우면서도 목표가 멀고 먼 느낌이 드는군요.. 아직은 계속 달릴 시기인거 같습니다. 다음에는 플레이어의 체력과 잔탄수를 UI로 추가해봐야겠습니다. 

Designed by Tistory.