우리 게임은 방치형 게임이기에 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();
   }

 

 

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

C# 질문

더보기

 

21 인터페이스와 추상클래스의 차이는 무엇인가요? 인터페이스는 행동에 대한 추상화가 필요할때 사용됩니다.
예상이 되지 않는 행동일때 많이 사용되며
A has B관계로 고양이 =/=걷다 이지만 , 고양이는 걷는 행동을 할 수 있습니다.

추상클래스는 구체적인 추상화가 필요할때 사용됩니다.
필드,메서드 를 부모,자식 사이에서 공통적으로 사용할때 많이 사용됩니다.
A is B관계로 , 고양이 = 동물 , 강아지 = 동물
22 가비지 컬렉터란 무엇인가요? GC란 Heap 영역에서 참조가 끊겨 사용하지 않는 데이터들을 수집하여
처리해주는 기능입니다.
23 가비지 컬렉터의 장점과 단점에 대해 설명해주세요. GC의 장점은 프로그래머가 신경을 덜 써도 되기에 실수 발생률을 줄여줍니다.
메모리의 할당 해지는 런타임시 매우 중요합니다.
즉, 프로그래머가 놓친 부분을 GC가 자동으로 다 처리해주는 것이 장점입니다.

단점으로는 GC는 처리비용이 높기에 결국 너무 의존해서는 안됩니다.
GC는 프로그래머가 원하는 호출시점을 알 수 없기에 런타임 되고 있는
프로그램이 어느순간 렉을 먹거나 하는 경우입니다.
24 가비지 컬렉터의 세대 개념에 대해 설명해주세요. GC의 세대 개념은 Java는 .NET에서 사용되는 개념입니다.
GC는 일반적으로 <세가지 세대>로 메모리를 나눕니다

0세대는 객체가 생성되면 할당됩니다.
1세대는 GC가 동작한뒤 사용되고 있는 0세대 객체들이 1세대로 승격합니다.
똑같은 동작을 반복하게 되고 1세대에서 살아남은 객체들은 2세대로 승격하게
됩니다.

즉, 2세대는 가장 큰 메모리 공간을 차지하게되며 계속 쌓이게 되면 오버헤드
문제점을 일으킬수도 있습니다.

크기가 큰 객체는 LOH(라지 오브젝트 heap)에 할당되에
일반적인 세대 기반 수집과는 다른방식으로 처리됩니다.
25 박싱, 언박싱을 사용할 때 주의해야 할 점은 무엇일까요? 박싱,언박싱은 결국 형변환의 일종이기에 박싱을 한 필드(변수)가 언박싱 될때
다른 자료형으로 변환시 잘못된 데이터가 들어가는 경우를 주의해야 합니다.
null값이 들어가게 될수도 있다.
이러한 단점을 방지하기 위해 제네릭을 사용 할 수 있습니다.
26 오브젝트 풀을 사용하면 메모리 관리에 도움이 되는 이유가 무엇일까요? 오브젝트를 Inisiate/Destroy를 반복하게 될경우 오브젝트의 생성/파괴시
처리비용이 높으며 , 파괴는 즉 GC의 수집량을 늘리는 상황이 됩니다.

즉 오브젝트 풀링을 사용하여 런타임시에 사용할 오브젝트를 미리 할당해놓고
사용하게되면 필요없는 생성/파괴가 일어나지 않기에 메모리 관리에 도움이
됩니다./
27 제네릭이란 무엇인가요? 제네릭이란 일반화로 , 클래스를 자료형에 상관없이 사용할때,
즉,다양한 데이터 타입으로 재사용 할수 있는 기술이며
자주 사용되는 Class를 변수처럼 사용하기 위한 용도로 보면됩니다.

28 람다식(Lambda Expression)이 무엇인지 설명해주세요. 람다식은 이름없는 함수로도 불리며,
원하는 호출시점에서 편하게 생성하여 사용 할 수 있는 기능이다.
단점으로는 이름이 없기에 다른 곳에서 호출하지 못하여 재사용성이 떨어지는
단점이 있다.
29 LINQ란 무엇인가요? LINQ를 설명하기전에 쿼리에 대해 알아야합니다.
쿼리는 DB에 정보를 요청하는 행위 ,즉 데이터를 필터링,정렬,조회 등등할때
사용되는 단어입니다.

LINQ는 .NET 프레임워크에 내장된 기능이며
사용하면 좀더 SQL, 컬렉션, XML, 엔티티 프레임워크 등의 데이터에
접근하여 쉽게 관리 할 수 있습니다.

하지만 LINQ의 단점으로는 간단한 데이터 조회시에는 괜찮지만
데이터가 복잡해지고 양이 많아지게 되면 퍼포먼스나 코드 가독성 면으로
떨어질수 있습니다.
30 리플렉션(Reflection)이 뭔지, 사용을 해봤다면 어떤 이유에서 사용했는지 설명해주세요. 리플렉션은 .NET에서 런타임에 메타데이터를 사용하여 클래스,메서드,
프로퍼티 등에 접근하고 조작할 수 있는 기능을 말합니다.

보통 코드를 빌드 과정에서 컴파일시에 해당 프로그램에 사용되는 자료형이
선언이 되는데, 해당 동작은 그 과정을 위반하고 런타임 중에 동적으로 생성하는것이다.

즉, 이러한 동작이 처리비용이 높고 , 컴파일 타임의 안정성을 저해하기 때문에
사용에 매우 신중을 가해야 된다고 생각합니다.

 

유니티 질문

더보기

 

1 Unity 생명주기(Unity Life Cycle)에 대해서 설명해주세요. Monobehaviour를 상속한 Class의
함수 호출 시점을 나타낸 것이다. 즉, 이 생명주기 함수들로
Class의 초기화,생성,업데이트,파괴를 할 수 있는것입니다.
2 MonoBehaviour 클래스의 주요 메서드와 그 기능에 대해 설명해주세요. 주요 메서드로 Awake와 Start,Update가 있습니다.
물론 이 밖에도 주요 메서드들은 더 있지만 이 3가지를
주로 사용하였기에 설명해보겠습니다.

2-1 MonoBehaviour 클래스에서 Start와 Awake의 차이점은 무엇이며,
이를 적절히 사용하는 방법에 대해 설명해주세요
Awkae와 Start의 주요 차이점은 호출 시점입니다.
Awake는 오브젝트가 활성화되자마자 호출됩니다.
Start는 오브젝트가 활성화 된 이후 첫 번쨰 프레임 업데이터 전에
호출됩니다. 즉, Awake보다 뒷 시점에 호출이 됩니다.

Awake는 스크립트간의 참조, 즉 초기화를 하기위해
주로 사용합니다.

Start는 Awake에서 초기화가 되고 난 후의 동작을 할때
주로 사용 하였습니다.
3 Update, FixedUpdate, LateUpdate의 차이점에 대해 설명해주세요. Update는 매 프레임마다 호출되는 메서드입니다.
FixedUpdate는 물리연산에 처리되는 메서드로 프레임률에 상관없이
일정한 간격으로 호출 됩니다.
LateUpdate는 프레임 종료직전에 한번 호출하게 되는 메서드입니다.
4 Time.deltaTime이란 무엇이며, 사용하는 이유에 대해 설명해주세요. deltaTime이란 프레임과 프레임 사이의 시간을 측정한것입니다.
이 델타타임이 필요한 이유는 기기의 성능에 따라 게임 플레이가
다른것을 보여주면 안되기 때문입니다.

간단히 말하면 핑이 튄다, 핑이 높다라는 표현이며 이 델타타임을 이용하여
기기성능에 상관없이 유저들에게 똑같은 게임 플레이를 제공 할 수 있습니다.
5 코루틴의 동작원리와 사용해본 예시를 함께 설명해주세요. 코루틴은 비동기라고 생각하지만 아닙니다. 메인 쓰레드에서 동작하며
비동기적으로 일시적으로 중단/재개 할수있는 기능입니다.

코루틴을 시작하게되면 메인 로직 동작중에 코루틴 시점이 오면
코루틴 함수가 동작하고 종료되면 다시 원복하여 메인 로직을
동작하게 하는 원리입니다.

사용해본 예시로 , Update 처럼 매 프레임을 체크할 필요는 없
6 Invoke와 코루틴의 차이에 대해 설명해주세요. Invoke는 인자값을 사용 할 수 없지만 코루틴은 인자값을 사용 할수 있습니다.
즉 , 간단한 시간 지연 함수 호출시에는 Invoke가 좋지만 , 로직 및 구조가
복잡해지고 확장성을 챙겨야 되는경우에는 코루틴이 적합하다고 생각합니다.
7 코루틴과 멀티쓰레딩은 어떤 차이가 있는지 설명해주세요. 코루틴은 메인스레드에서 동작하며 비동기적으로 동작하는거고
멀티쓰레딩은 스레드를 여러개 생성하여 비동기로 동작합니다.
즉, 여러 로직(렌더링,서버)를 병렬로 작업하여 효율성을 증가합니다.
8 유니티 최적화 기법은 어떤 것들이 있나요? 여러장의 스프라이트를 하나의 파일로 관리하는 스프라이트 아틀라스 및
거리에 따른 렌더링 최적화 기법인 LOD 기법 , 카메라의 영역에 있지 않으면
렌더링하지 않는 오클루전 컬링 기법 등을 알고 있습니다.
8-1 최적화를 해본 적이 있나요? 없다면 어떤 최적화가 있는지 설명해주세요. 2D 슈팅게임을 만들며 , 플레이어 캐릭터 및 적,투사체의 Sprite리소스를 Sprite
아틀라스로 만들어 사용했던 적이 있습니다. 해당 기능을 사용하여
기존에 Sprite를 사용했던것보다 SpriteAtala를 사용하여 드로우콜을 최적화한
경험이 있습니다.
8-2 최적화에서 가장 중요한 부분은 무엇인가요? 해당 최적화에서 중요한 부분은 DrawCall를 최소한으로
호출하는것입니다. 렌더링이 아주 큰 처리비용을 가지고 있기에
최소한의 DrawCall을 사용해야됩니다.
8-3 최적화를 위해서 적용해본 텍스쳐 포맷이 있나요? 텍스쳐 포맷은 따로 사용해본적은 없습니다. 하지만 필요로 해야되는 기능이기에
꼭 익혀서 자신의 것으로 만들어놓겠습니다.
9 드로우콜에 대해서 설명하고, 최적화하는 방식에 대해 알고 있는 것이 있으면 설명하세요. 드로우 콜은 CPU가 현재 프레임에 무엇을 그릴지 결정하고
GPU에 그리게하는 것입니다.

제가 아는 최적화 방식은 LOD(Level of Detail) 기법이 있습니다.
GPU는 하나의 폴리곤을 그리기 위해 가까이 있든 멀리있든 해당 메쉬의 크기에 맞춰
그리게 됩니다. 하지만 이렇게되면 쿼드오버드로우 현상이 발생하게 되고 멀리 조그만한
점을 그리기 위해 4개의 픽셀을 사용하게되는 비효율적인 상황이 나옵니다.

즉 멀리있는 폴리곤은 그리지 않는것이 LOD 기법의 특징입니다.
10 Find 함수 사용을 자제해야 하는 이유에 대해 설명해주세요. FInd 함수는씬에 존재하는 모든 오브젝트를 탐색하기에
시간 복잡도 및 처리 비용이 매우 높기 때문입니다. 대처법으로는 하이어라키에
자식 오브젝트로 부모오브젝트가 필요한것을 배치하거나 컴포넌트를 사용하는
방법이 있습니다.

현재 가지고 있는 재화에 따라 스탯 레벨업이 적용된다.

 

PlayerInfoModel.cs

    public void HpLevelUp(int amount)
    {
    	//플레이어가 가지고 있는 골드가 업그레이드 코스트보다 높으면 true
        if (GameManager.Instance.player.UserData.Gold >= Utils.UpgradeCost(Status.Hp))
        {
        	//플레이어 골드 - 업그레이드 코스트 비용 적용
            GameManager.Instance.player.UserData.Gold = 
                Mathf.Max(0, GameManager.Instance.player.UserData.Gold - BigInteger.ToInt32(Utils.UpgradeCost(Status.Hp)));
            //플레이어 레벨업 => 스텟 증가량 및 스테이터스 타입을 전달하여 스탯 증가
            GameManager.Instance.player.LevelUp(amount, Status.Hp);
            OnHpUpgrade?.Invoke(); //View에 데이터를 전달하여 출력을 갱신함
        }
    }

 

Utils.cs에 있는 static Class 

 /// <summary>
 /// 스텟 업그레이드시 적용되는 코스트 증가율 수식
 /// </summary>
 /// <param name="baseCost">초기 비용. 레벨 1에서의 기본 비용입니다.</param>
 /// <param name="level">현재 레벨</param>
 /// <param name="growRate">코스트 증가율 (예: 1.5~2.0 사이).</param>
 /// <param name="constantIncrease">레벨마다 고정적으로 추가되는 비용 (선택 사항).</param>
 /// <returns></returns>
 public static BigInteger UpgradeCost(Status statType)
 {
     int baseCost = 0;
     float growRate = 0;
     int playerStatLevel = 0;
     int constIncrease = 0;

     switch (statType)
     {
         case Status.Hp:
         	 /*필요한 데이터를 StatUpgradeDB에서 호출해서 사용 */
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).MaxHealthBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).MaxHealthGrowRate;
             /*플레이어의 스텟 레벨을 호출 */
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.MaxHealthLevel;
             constIncrease = 20;
             break;
         case Status.Atk:
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).AtkBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).AtkGrowRate;
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.AtkLevel;
             constIncrease = 20;
             break;
         case Status.Def:
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).DefBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).DefGrowRate;
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.DefLevel;
             constIncrease = 20;
             break;
         case Status.ReduceDmg:
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).ReduceDamageBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).ReduceDamageGrowRate;
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.ReduceDamageLevel;
             constIncrease = 20;
             break;
         case Status.CritChance:
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).CriticalRateBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).CriticalRateGrowRate;
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.CriticalRateLevel;
             constIncrease = 20;
             break;
         case Status.CritDmg:
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).CriticalDamageBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).CriticalDamageGrowRate;
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.CriticalDamageLevel;
             constIncrease = 20;
             break;
     }
     
     //코스트가 증가하는 기본적인 적용 수식 중 하나 
     int Cost = (int)(baseCost * Mathf.Pow(playerStatLevel, growRate) + (playerStatLevel * constIncrease));

     return new BigInteger(Cost);

 }

 

Cost가 레벨에 증가함에 따라 서서히 증가하는 함수가 필요 했기에 검색해보니

방치형 게임에서 주로 사용되는 수식이 하나 존재하여 사용하였다. 

 

빠른 성장 속도의 게임  GrowRate ▲ BaseCost ▽
느린 성장 속도의 게임 GrowRate ▽ ConstantIncrease ▲
성장 속도의 커브를 조정 
(제곱하는 자리에 해당 함수를 대입하면됨)
로그함수(y = logx) 적용 -> 증가폭이 점점 줄어듬 지수함수(y=x^2) -> 증가폭이 점점 상승함 (현재 적용중인 함수)

 

 

+ Recent posts