Deff_Dev

[Unity/C#] 확률 구하기 본문

Unity(유니티)/유니티 공부

[Unity/C#] 확률 구하기

Deff_a 2024. 6. 24. 22:31

 

확률을 설정할 때, 모든 확률의 합은 반드시 100%가 되어야 한다.

하지만 직접 데이터 테이블을 수정하다 보면 이 조건을 지키지 못하는 경우가 생길 수 있다.

 

확률의 합이 100%보다 작거나 크면 안 되기 때문에, 합이 100%보다 작을 경우 부족한 수만큼 더해서 100%를 맞추고, 100%를 초과할 경우 초과된 만큼 빼주는 로직을 구현했다.

 

물론, 엑셀을 사용해 확률을 관리할 수도 있지만, 이번 프로젝트에서는 인스펙터를 통해 확률을 입력하기로 했기 때문에 이 기능을 추가하게 되었다.

 

 

중요 클래스 - Random - Unity 매뉴얼

Random 클래스는 흔히 요구되는 다양한 타입의 랜덤 값을 쉽게 생성할 수 있는 방법을 제공합니다.

docs.unity3d.com


확률을 표현하는 방법

 

int 자료형을 사용해 랜덤 값을 1부터 (1000, 10000 등) 사이에서 표현하는 방법

 

 장점

  • 성능: float 형변환이 필요 없기 때문에 성능이 약간 더 좋습니다.
  • 구현 용이성: 구현하기가 상대적으로 쉽습니다.

단점

  • 이해도: 랜덤 값을 돌리는 최대 숫자에 따라 1이 1%, 0.1%, 0.01% 등으로 변할 수 있어 처음 봤을 때 이해하기 어려울 수 있습니다.

 

float 자료형을 사용하여 확률을 표현하는 방법

 

장점

  • 이해도: 확률을 직관적으로 이해할 수 있다. 예를 들어, 15.7%를 바로 입력할 수 있다.

단점

  • 형 변환: float 형인 확률 값을 사용할 때, 이를 (10 * 표현하고자 하는 소수점 자릿수)로 곱한 후, int로 형변환해야 합니다. 이 후에 랜덤 값을 구하는 과정을 거쳐야 한다.

 

해당 프로젝트는 팀 프로젝트 이기 때문에 float 형을 사용하는 후자의 방법을 사용해봤고 소수점은 첫 번째 자리까지만 사용하는 것으로 정했다.


스크립트 작성

[Serializable]
public class StageSpawnPercent
{
    public float[] spawnPercent = new float[3];
    
    public void CalculatePercentage() // 합계가 100%가 되도록 계산해주는 함수
    {
        float perSum = 0f;
        for (int i = 0; i < spawnPercent.Length; i++) 
        {
            if (perSum < 100f)
            {
                if (perSum + spawnPercent[i] >= 100f) // 100퍼센트가 넘는다면
                {
                    spawnPercent[i] = 100f- perSum; // 해당 퍼센트를 100퍼센트가 되도록 맞춰줌
                    perSum = 100f;
                }
                else
                {
                    perSum += spawnPercent[i]; // 더해줌
                }
            }
            else
            {
                spawnPercent[i] = 0; // 합이 100%가 넘을 떈 0으로 고정
            }
        }

        if (perSum < 100f) // 100퍼가 안된다면 맨 마지막 확률이 나머지 퍼센트를 더해줌
        {
            spawnPercent[spawnPercent.Length - 1] += 100 - perSum;
            spawnPercent[spawnPercent.Length - 1] = Mathf.Round(spawnPercent[spawnPercent.Length - 1] * 10) / 10;
        }
    }
}

 

위 클래스를 작성하고 Awake에서 해당 클래스의 CalculatePercentage 함수를 호출한다.

 

using UnityEngine;

namespace RandomEvent
{
    public class RandomEventResult
    {
        public int GetArrRandomResult(float[] percent) // 퍼센트 배열이 존재할 때 호출
        {
            float random = GenerateRandomFloat (1f,100f); 
            float percentSum = 0;
            int result = 0;

            for (int i = 0; i < percent.Length; i++) // 확률 계산
            {
                percentSum += percent[i];
                if (random <= percentSum)
                {
                    result = i;
                    break;
                }
            }
            return result; // 결과 값 리턴
        }

        public bool GetBoolRandomResult(float percent) // bool 값이 필요한 랜덤일 때, 호출
        {
            float random = GenerateRandomFloat (1f,100f);

            return random <= percent ? true : false;
        }
        
        private float GenerateRandomFloat(float min, float max)
        {
            float randomValue = Random.Range(min, max);
            float roundedValue = Mathf.Round(randomValue * 10f) / 10f; // 소수점 1 번째 자리로 반올림 해줌
            return roundedValue;
        }
    }
}

 

랜덤 이벤트 클래스를 RandomEvent 이름 공간(namespace)로 구현한다.

 

 

using System.Collections.Generic;
using UnityEngine;
using RandomEvent;

public class EnemyDataManager : MonoBehaviour
{
    [Header("# Enemy Spawn Percent ")]
    [SerializeField] private StageSpawnPercent stageSpawnPercent;
    
    private RandomEventResult randomEvent;

    private void Awake()
    {
        randomEvent = new RandomEventResult();

        for (int i = 0; i < stageSpawnPercent.Length; i++)
        {
            stageSpawnPercent[i].CalculatePercentage();
        }
    }

    public int GetEnemyType()
    {
        int enemyType = randomEvent.GetArrRandomResult(stageSpawnPercent[spawnPercentLevel].spawnPercent);
        
        return enemyType;   
    }
}

 

 using RandomEvent를 작성한 뒤, RandomEventResult 클래스를 선언 및 초기화한다.

 

 그리고 내가 구하고자하는 확률을 RandomEventResult에 저장된 함수들을 이용하여 구하면 된다. 


 

이걸 구현해보고 느낀 점은 float 보다는 int가 더 구현하기 쉬운 것 같고, 굳이 float로 할 이유를 못 찼겠다.

 

같이 작업하는 팀원들과 협의하고 int로 진행한다면 큰 문제는 없을 것 같다고 생각한다.