공부/Unity

[Unity] For The King 확률 구현 - 유니티에서 팩토리얼, 컴비네이션 구현

굴러다니다니 2023. 5. 18. 12:04
728x90

팀 프로젝트 - For The King 모작 과정 (확률)

 스팀 게임 For the king을 보면 몬스터든 상호작용 오브젝트든 각자의 종류가 존재하고 이 종류에 맞는 스탯만큼 확률이 나온다.

사진을 보면 눈 스탯에 맞는 종류이며 3칸 중 3칸을 성공 할 확률이 42%라고 나와있다.

이 확률은 무엇을 기반으로 나온 계산일까

 

이 캐릭터의 눈 스탯을 보면 75이고, 슬롯 하나당 성공 할 확률이 75%, 이를 전부 성공할 확률이 42%라는 것이다..,?

 

이를 구현해보기 위해 확률과 통계의 계산법을 떠올려야한다

 

하루에 비가 올 확률이 20%라면 안 올 확률은 80%이다.

만약 3일 내내 비 올 확률을 구하라면 0.2 * 0.2 * 0.2 * 100 해서 8%가 되겠지만

하루만 비 올 확률을 구하라고 하면 (하루 비오는 확률) 0.2 * (이틀 비 안오는 확률) 0.8 * 0.8 * 100 이 전부가 아닌

3일중 비오는 하루를 구하는 경우의 수가 3C1로 3이기에 *3 을 더해줘야 한다.

이를 코드를 이용해 작성해보자.


일단 Combination은 팩토리얼로 이루어진 공식이기 때문에 Factorial  부터 구현을 해주자

private int Factorial(int n)
    {
        int result = 1;
        for (int i = 1; i <= n; i++)
        {
            result *= i;
        }
        return result;
    }

위의 식은 n을 받아 1부터 n까지를 곱해 이를 출력하는 함수다 간단!

 

이제 콤비네이션 함수의 식을 살펴보자

서칭하다 나온 식인데 그냥 오른쪽처럼 팩토리얼을 넣어주면 할 수 있다고 한다

 

public int Combination(int a, int b)
    {
        return Factorial(a) / Factorial(b) / Factorial(a - b);
    }

위의 코드는 aCb를 한 코드로서 위의 내용을 그냥 고대로 옮겨왔다.

 

그렇다면! 저 위의 포더킹 확률은 어떻게 계산할 수 있을까?

 

사진을 다시 보면 일단 최대 슬롯의 개수가 3개이다.

하지만 이는 변동될 수 있으므로 maxSlot 변수로 선언을 해두겠다.

또한 슬롯 하나당의 확률은 플레이어마다 다를 것이다 -> 마찬가지로 percent라는 변수로 선언을 해두겠다.

마지막으로 오른쪽의 창을 보면 3칸 중 3개를 성공해야 성공으로 인정하는데, 이는 3칸 중 2칸만 성공해도 인정하는 경우 등이있으므로, 성공의 최소 limit을 변수로 선언해 successLimit이라는 변수로 선언했다.

 

private void Calculate(int maxSlot, int percent, int successLimit)
    {
        for (int i = maxSlot; i >= 0; i--)
        {
            float successPercent = percent * (float)0.01;
            float failPercent = (100 - percent) * (float)0.01;
            float result = Mathf.Pow(failPercent, maxSlot - i) * Mathf.Pow(successPercent, maxSlot - (maxSlot - i)) * formula.Combination(maxSlot, i) * 100;
            result = Mathf.Round(result);
            if (i >= successLimit)
            {
                Debug.Log(i + " " + result + " 성공");
            }
            else
            {
                Debug.Log(i + " " + result + " 실패");
            }
        }
    }

그렇게 작성된 식은 위와 같으며 하나하나 살펴보겠다.

 

일단 사진을 다시 보면 위에부터 3 2 1 0 순서로 작아지기 때문에 마찬가지로 i를 작아지게 만들었고,

성공 확률을 따로, 실패 확률을 따로 두어 계산을 진행했다.

 Unity 제공 함수인 Mathf.Pow는 그냥 거듭제곱 함수로 앞의 수를 뒤의 수만큼 제곱한 것이다.

따라서 result는 실패확률 ^ (전체 중 i를 뺀 만큼의 실패한 횟수) * 성공확률 (전체 중 실패횟수를 제외한 횟수) * 전체횟수 Combination i * 100 (백분율로 바꿈) 이라는 식을 통해 나오게 된다.

그냥 단순히 위에서 말로 서술해 둔 부분들을 변수로 선언한게 전부이다.

하지만 이렇게 나온 결과는 소수점값을 더 가지고 있기 때문에 Mathf.Round를 통해 반올림을 해주고, 만일 i가 성공 제한보다 크면 뒤의 텍스트를 성공, 아니면 실패로 나오게 만들었다.

 

private Formula formula;

    [SerializeField] private int testMax = 3;
    [SerializeField] private int testPercent = 86;
    [SerializeField] private int testSuccess = 3;

    private void Start()
    {
        formula = FindObjectOfType<Formula>();
        Calculate(testMax, testPercent, testSuccess);
        //슬롯의 칸, 슬롯 하나의 성공 확률, 슬롯 칸중에 몇개의 확률을 통과해야 하는지
    }

start 콜백함수에다가 formula (combination, factorial 정의해 둔 스크립트)를 불러오고, Calculate 함수에 외부에서 받은 testMax, testPercent, testSuccess를 넣어 실행해준다.

 

위의 인게임 스크린샷의 순서와 같이 잘 작동하는 것을 확인할 수 있다.

 


아아 여기서 그걸 안 썼다

포더킹에서 이상한 부분이 있다

침착맨 유튜브 가져옴

여기서 보면 슬롯 하나당 확률이 71%이다.

(36%) 성공 된 확률도 0.71 * 0.71 * 0.71 * 100 에서 나온 확률이다

 

하지만 집중력을 하나 사용해 한 칸의 슬롯을 확정적으로 사용한다면 어떻게 될까?

남은 슬롯은 두개이고, 슬롯 하나당 71%를 그대로 해서 0.71 * 0.71 * 1 * 100 으로 50% 쯤이 될 줄 알았는데!

 

어라라? 66%?

이것이 무엇인고

위에서 내가 만든 스크립트를 사용해 알아보니 슬롯당 확률이 81%이고, maxSlot이 두 칸이 되면 위와 같은 결과가 나왔다.

 

게다가 집중력을 두 칸을 사용하면 무려 86%라는 성공이 측정됐다.

이는 한 칸의 슬롯이 성공률이 86%라는 소리인데...

단순히 다른 매커니즘으로 계산을 한 줄 알았다

 

하지만 전투씬에서도 슬롯당 정확도가 83%에서

 

93%로 올라가는 것을 보니 집중력을 한 칸 사용하면 10%, 두 칸이면 15%, 등으로 그냥 특정 수치만큼 올라가는 것으로 추정이 되었고, 이를 반영해 코드를 고쳤다.

 

아까 쓴 코드에서 testFix 변수를 추가해주었다.

 

또한 Calculate 메소드 안에서 fix값이 1이라면 메서드 내에서 계산하는 퍼센트 값을 10을 더 증가, 이런 식으로 추가로 작성해주었다.

 

또한 위에서 작성했던 식을 일부 변형해주었다.

for문에 들어가서 확률을 계산하기 전, maxSlot에서 집중력을 쓰는 만큼을 제외해주었다.

만일 maxSlot과 사용한 집중력이 같다면 maxSlot은 0이 될 것이며, 이 상황에서는 모든 확률값이 100%일것이다.

그리고 i번째가 성공 제한에서 집중력을 뺀 것과 이상이라면 그냥 성공을 출력하게 만들었다.

이렇게 된 이후에 친구들은 0%이므로 아래 for문을 추가해 써줬따

 

버튼으로 실험해보고싶어서 함수도 추가!

각각 누르면 사용한 집중력의 개수를 올리고, 집중력 개수를 리셋한다

 

또한 블로그에 쓰진 않았지만 확률에 따라 슬롯을 보여주는 싱글톤 선언해둔 SlotController와 연결해준다.

 

이렇게 해준 뒤 버튼을 만들어 OnClick에 FixAdd, FixReset을 연결했고

나머지 하나의 버튼에는 SlotController가 작동하게 만들었다.

 

71%의 확률로 실행 한 결과 위의 사진과 같은 확률을 보여주었고, 실제로 나는 44% 확률에 걸쳐 실패했다 (2개만 확률 통과)

 

집중력을 한 번 사용한 뒤에 보면 66%까지 승률이 올라갔고

 

집중력 두 번

 

집중력 세 번 사용했을때의 결과이다

이를 이제 UI와 연결하여 이쁘게 표현만 하면 될 것 같다! 휴!

728x90