1. 도전과제 저장/불러오기 구현 : 도전과제를 클리어했거나 업적 진척도를 Json으로 역직렬화를 하여 저장과불러오기를 구현하였음

 

2.플레이어 사망시 PopUp UI 비활성화 : 플레이어 사망시 동그랗게 마스크처리가 되는데 , 해당 영역에 PopUP창이 계속 유지 되는 상태였음 -> UIManager에 플레이어가 Die시 모든 PopUP UI를 비활성화 처리하는 메서드를 만들어 적용 시킴

 

3.수동조작 상태일때 ,플레이어 사망시 슬라이딩 하는 버그 : 플레이어 사망시에 cuyInputMove라는 vector2 변수 데이터가 남아있어 플레이어 사망 -> 게임오버 로직 처리시에 해당 변수를 0,0으로 초기화 함

 

4.Enemy를 대상으로 플레이어가 와라가리하는 경우 Enemy의 공격 노쿨 버그 : 플레이어가 FSM 공격 상태일때 계속 쿨타임이 초기화 되는 버그가 발생하여 , FSM 생성자에서 한번만 바로 공격할수 있게 구성한 뒤 그 이후에는 해당 공격의 쿨타임을 저장하고 있어 공격상태일때만 공격쿨타임이 줄어들게 설계함, 이렇게되면 와리가리를 해도 Enemy는 벙찌는 현상을 줄일수 있음 

 

5.아이템 획득시, 강화 스택량이 저장할때 불러오는 데이터가 다른 버그 : 아이템 획득시 스택이 1/1 인데 , 게임 종료후 불러오기를 하면 1/3 이 되어있음 , 불러오기시 해당 변수의 데이터를 매직넘버로 넣고있었기에 발생한 버그 , 변수화 하여 데이터를 넣게 리팩토링 함 

저장 데이터가 존재하지 않으면 이름입력 란이 뜨며 입력을 입력할 수 있게 구현됨

 

이름 입력을 받아 플레이어 정보창에 이름이 표기되고 저장/불러오기가 가능해짐

 

2025.01.08 작업 내용

1.이름 입력시 받는 데이터 전달 작업

2.몬스터들이 이제 플레이어를 발견하면 바로 공격을 실행합니다. (밸런스 조정 -> 코드수정 보다는 공격 애니메이션의 event Trigger 시점을 변경함)

3.스테이지에 몬스터가 등장하지 않는 버그 수정 (몬스터 수 제한 조건을 걸어놓구 초기화가 되어있지 않음)

4. UI 위치 조정 및 골드, 다이아몬드 재화 자료형 long으로 변경 -> 골드와 다이아몬드의 재화량이 21억(int) 자료형을 넘어서는 경우가 있어 더욱 큰 long 자료형을 사용하였음 

우리 게임은 방치형 게임이기에 BigInteger를 사용하여 큰 정수들을 관리하고 있는데 , 

int 자료형으로는 값 감당이 되지않아 ulong으로 변경하여 사용하였다 .

 

1:1 전투시에는 문제가 없지만

1 : 다 전투 + 다단히트 + 광역 기술을 사용할경우 콜라이더 충돌 처리 하여 데미지 계산을 함과 동시에

BigInteger 클래스 -> ulong 으로 변경할때 너무 많은 연산처리가 일어나 유니티 크래쉬가 일어난다. 

 

 

이전 코드

   public void TakeDamage(BigInteger damage, StatHandler statHandler)
    {
        var maxHelth = BigInteger.ToUInt64(statHandler.CurrentStat.maxHealth);
        var curHelth = BigInteger.ToUInt64(statHandler.CurrentStat.health);

        var result = BigInteger.ToUInt64(damage);
        if(curHelth - result > result) //unsinged 자료형이기에 
        {
            statHandler.CurrentStat.health = 0;
        }
        else
        {
            statHandler.CurrentStat.health = curHelth - result;
        }
       

        HpUpdate();
    }

BigInteger.ToUInt64를 통해 변환하는과정을 겪는데, 해당 과정의 처리비용이 정말 높다.

하지만 Int 값으로 조정될떄는 처리비용이 낮다고는 할수없지만,  ulong으로 변경되며 처리비용을 생각하지 못하고 

해당 코드를 그대로 작성하여 사용한 결과 

 

유니티 크래쉬가 발생하며 , 원인이 어디서 발생했는지 찾기가 어려웠다. 

 

찾기위해서 유니티 크래쉬가 일어나는 시점을 찾기위해 런타임 + 디버그 모드를 통해 크래쉬 발생 지점을 유추하고 

해당 코드를 보면서 대략적으로 몇번의 루프문이 도는지 , 오버플로우가 일어나는지 생각해보았다. 

 

근데 이 코드에서는 변환과정이 필요하지 않은데 억지로 변환하여 사용하고 있고 또한 그 전 메서드에서 damage 값을 처리해서 오는데 그걸 또 비교문을 통해서 동작하고 있었던것이 원인이였다. 

 

개선된 코드

  //플레이어와 Enemy가 공동으로 사용하는 TakeDamage 메서드 
  
  public void TakeDamage(BigInteger damage, StatHandler statHandler)
  {
      statHandler.CurrentStat.health = statHandler.CurrentStat.health - damage;
      HpUpdate();
  }

 

해당 코드로 변경하고 나서 많은 연산처리가 줄어 정상적으로 유니티크래쉬가 발생하지 않게 되었다. 

유저테스트를 통해 유저분들게 피드백을 받았고 피드백으로 많이 받은 것은

 

1. 투사체 오브젝트 풀링 문제
2. 스탯 UI 적용 오류 문제

3. UI/UX가 불친절하다는 문제

4. 레벨디자인이 너무 쉽다.

여러 문제점이 있지만, 4가지 문제가 주를 이뤄었다.
최종 발표까지 남은 기간동안 해당 피드백들을 개선하며 버그를 고쳐 잡아야될것같다. 

 

2. 스탯 UI 적용 오류 문제 - > 해당 문제는 플레이어의 레벨업 수치와 아이템 및 소울 패시브로 추가되는 스탯을 분리하지 않아 일어나는 버그였다 .

 

BaseStat => 플레이어의 레벨업만 적용되는 스텟

CurrentStat => Item,Soul 패시브 스탯 + BaseStat 

 

이렇게 두가지의 Stat으로 나눠 리팩토링을 진행 하였고 .

플레이어 인포 UI도 총합 스탯과 기본 스텟 두가지로 나눠 가독성을 올려주었다. 

 

4. 레벨디자인이 너무 쉽다. -> 레벨디자인이 너무 쉬운 문제는 Enemy들의 스텟을 Stage의 넘버링에 따라 배율을 다르게

설정해놨는데 해당 수치를 올렸고 , 플레이어의 성장곡선 및 업그레이드 비용이 지수함수였으나 로그 함수로 변경하여 점점 게임을 할수록 레벨 디자인을 어렵게 만들었다. 

 

지수함수 공식을 주석처리하고 , 로그함수로 대체함

 

 

WebGL로 빌드해서 정상적으로 게임 플레이가 되는것은 확인은했지만

저장이 될떄가 있고 안될떄가 있다. 

 

현재 프로젝트는 Application.persistentDataPath 메서드를 사용하여 Json으로 파일을 관리하고있다.

 

WebGL의 persistentDataPath 특징

  1. 경로: WebGL에서는 실제 경로를 제공하지 않고 기본적으로 "idbfs/"라는 가상 파일 시스템을 사용합니다.
    • 예: Application.persistentDataPath는 항상 "idbfs"로 설정됩니다.
  2. IndexedDB 저장소:
    • Unity는 데이터를 IndexedDB에 저장하며, 브라우저를 통해 데이터가 유지됩니다.
    • 특정 조건(예: 브라우저 캐시 삭제)에서는 데이터가 손실될 수 있습니다.

 

WebGL의 저장 작업이 안되는 이유

1. WebGL에서 IndexedDB는 비동기 방식으로 작동하므로, 파일 작업이 지연될 수 있다.

2. 브라우저 간 데이터 호환성이 없으므로, 동일한 브라우저에서만 데이터가 유효하다.

 

즉, WebGL에서 저장 데이터를 관리하기 위해서는 약간의 시간이 걸릴 수 있다.

해당 문제를 처리하기 위해서는 WebGL 동기화 에 대해 알아보고 리팩토링을 적용 시켜보아야할것 같다 .

Soul 이라는 객체의 데이터를 저장하는 Json 파일 구조
Player.cs의 로드할 데이터 유무 케르하여 해당 데이터를 파싱하여 사용한다.
SoulInventory.cs로 Player의 멤버변수의 PlayerSoul 멤버변수로 가지고있는 Stat메서드이다.

해당 Start메서드에서 게임매니저에 있는 플레이어의 SoulInventory에 자기자신을 참조시킨뒤 게임의 불러오기 데이터 유무를 체크 하여 상황에 따라 Soul을 초기화 한다. 


 

ItemStatusController.cs의 아이템을 강화하는 메서드 영역 , 현재 List에서 Find 메서드를 사용하여 객체를 탐색하고있다.

List의 탐색의 시간복잡도는 (n)이고 Dictnary의 탐색 시간 복잡도는 1이다. 그러기때문에 List -> Dict으로 리팩토링을 진행해야된다. 그 와중에 해당 데이터는 직렬화 과정을 겪고 있고 Dictnary는 직렬화에 사용하지 못하기에 잠시 트러블 슈팅을 겪었고 List로 대체되었는데 , 튜터님께 물어본 결과 List -> Dict로 전환하는 처리비용이 들지만 결국 탐색을 자주하면 할 수록 최적화에는 Dict에 더 도움이 된다고 하셨기에 Dictnary로 리팩토링을 진행 하여야 한다. 

 

즉, 직렬화/역직렬화(저장/불러오기) 시에만 List <> Dictnary로 전환 할 수 있게 리팩토링 하면된다. 

 


 

 

Player객체가 움직일때 발소리가 나는 AudioSource 컴포넌트가 필요하였기에 자식 오브젝트로 하나 생성하여 해당 오브젝트는 계속 발소리를 내게 구현하였고 나머지는 각 상황에서 필요할때마다 호출하는식으로 구현하였다. 

Json으로 저장되는 UserData

오늘은 캐릭터 스텟뿐만 아닌 플레이어가 가지고있는 아이템을 Json으로 저장하였고

실제 게임에 적용될때 해당 데이터를 통해 스텟을 원복하는 코드를 구현 하였다. 

 

InventoryModel.cs :: UIInventory의 데이터를 담당하는 cs

저장된 .json 파일의 Item리스트를 불러와서 내가 소지한 아이템 ID와 같으면 내 인벤토리에 등록 및 패시브 효과를 적용 시킨다.


Json에서 소지한 아이템(GainItem) 리스트를 읽어와서 LoadInData에서 해당 Stat 및 데이터를 원복한다. 

 

Item.cs

LoadIndata를 이용하여 해당 아이템의 스텟 및 데이터들을 원복하는 작업을 진행 후

 

내 인벤토리에 해당 아이템을 활성화 시키는 작업을 구현 하였다.

 

JsonController.cs

 

JsonController를 이용하여 Json의 데이터를 저장/로드를 구현하였다. 

 

저장할때는 사용하던 데이터를 Json형식으로 변경해줘야하는데 

 

UserDB.cs의 JsonDataConvert 메서드

해당 메서드를 활용하여 직렬화 가능한 데이터들을 파싱하여 사용하였다. 

아이템 소지 갯수로 강화가 가능하며 보유효과(패시브)와 장착효과(액티브)로 구현하였음

 

using System;
using Unity.VisualScripting;
using UnityEngine;

[System.Serializable]
public class Item : BaseItem
{
    [SerializeField] private int ID; // Item ID, 직렬화 하기 위한 
    public bool IsGain; //플레이어의 아이템 첫 소지여부
    public bool equip; //플레이어의 아이템 장착 여부
    public int stack; //아이템 소지 갯수 

	//해당 부분 추가
    public Stat PassiveStat; //아이템 소지 효과
    public int PassiveStatValue = 5; //아이템 스텟의 n%가 패시브 효과로 적용됨 
	//
    
    public int UpgradeLevel; // 아이템 레벨 
    public int UpgradeLevelMax; //아이템 강화 최대 레벨
    public int UpgradeStackCount; //아이템 강화 스택 필요양 
    public int UpgradeStatIncreaseRatio; //아이템 강화시 스텟 증가량
    public int UpgradeCostIncreaseRatio; //아이템 강화 스택 증가량
    public override void Initialize(ItemDB data)
    {
        base.Initialize(data);

        PassiveStat = ItemStat / PassiveStatValue; // 패시브는 고유효과의 n퍼센트 효과를 지님

        ID = itemStat.iD;
        //ToDo : 플레이어 유저데이터를 참고하여 갱신할수 있게 해야됨
        IsGain = false;
        stack = 9999;
        UpgradeLevel = 1;
        UpgradeLevelMax = 10;
        UpgradeStackCount = 1;
        UpgradeStatIncreaseRatio = 2;
        UpgradeCostIncreaseRatio = 3;
    }

}

 

ItemStatusController.CS의 일부 

   private void UpgradeItem()
   {

       if (SelectItem.item.UpgradeLevel < SelectItem.item.UpgradeLevelMax&& SelectItem.item.stack >= SelectItem.item.UpgradeStackCount)
       {
           SelectItem.item.stack -= SelectItem.item.UpgradeStackCount;
           SelectItem.item.UpgradeStackCount *= SelectItem.item.UpgradeCostIncreaseRatio;

           SelectItem.item.UpgradeLevel++;
           SelectItem.item.ItemStat.maxHealth *= SelectItem.item.UpgradeStatIncreaseRatio;
           SelectItem.item.ItemStat.atk *= SelectItem.item.UpgradeStatIncreaseRatio;
           SelectItem.item.ItemStat.def *= SelectItem.item.UpgradeStatIncreaseRatio;
           SelectItem.item.ItemStat.reduceDamage *= SelectItem.item.UpgradeStatIncreaseRatio;
           SelectItem.item.ItemStat.critChance *= SelectItem.item.UpgradeStatIncreaseRatio;
           SelectItem.item.ItemStat.critDamage *= SelectItem.item.UpgradeStatIncreaseRatio;
			
           //아이템의 효과에 n퍼센트 적용 수치 
           SelectItem.item.PassiveStat = SelectItem.item.ItemStat / SelectItem.item.PassiveStatValue;
       }

       UpdateView();
   }

 

 

해당 두 코드를 이용하여 스텟을 적용 시키는 로직을 구현 하였다.

+ Recent posts