본문 바로가기

유니티/Performance

유니티 - ref, out, (in + 데미지 최적화)

우선 C# 에서는 ref, out, in 키워드가 있다.

ref와 out은 C#에서 값 타입을 함수에서 참조처럼 다루고 싶을 때 사용하는 키워드!

 

키워드 설명                                                        사용조건             

ref 읽고 쓸 수 있는 참조 전달 변수는 메서드 호출 전에 반드시 초기화되어 있어야 함
out 메서드가 값을 설정해서 반환할 용도 메서드 안에서 반드시 값을 설정해야 함
in 읽기 전용 참조 전달 (성능 최적화) 메서드 내부에서 변경 불가 (읽기만 가능)

 

그럼 언제 사용을 하는가?

[ref]

public class Player
{
    private int _hp = 10;

    // hp 접근을 위한 메서드
    public ref int GetHPRef()
    {
        return ref _hp;
    }
}

public class Manager : MonoBehaviour
{
    public static Manager Instance;

    private void Awake()
    {
        Instance = this;
    }

    public void Heal(ref int hp, int amount)
    {
        hp += amount;
    }
}

public class Item : MonoBehaviour
{
    public Player player;

    private void Function(Player player)
    {
        ref int hpRef = ref player.GetHPRef(); // ref로 hp 참조 가져오기
        Manager.Instance.Heal(ref hpRef, 10);  // ref로 전달해서 직접 수정
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        Function(player);
    }
}

 

이런식으로 Player class 내부에서 Heal이 달려있으면 참조해서 사용하면 되지만 공용으로 사용되는 Heal으로 해결할 수 있다면 ref 키워드를 사용해서 처리할 수 있다. (반드시 호출 전 값이 초기화 되어 있어야 함)

단, 구조가 복잡해질 경우엔 캡슐화를 유지하는 게 더 중요할 수 있으니 상황에 따라 유동적으로 써야 한다.

 

[out]

public class Player
{
    private int _hp = 10;

    // hp 반환 메서드
    public int GetHP()
    {
        return _hp;
    }
}

 

public class Manager : MonoBehaviour
{
    public static Manager Instance;

    private void Awake()
    {
        Instance = this;
    }

    // 여러 값을 반환하려면 여러 개의 out 파라미터를 사용
    public void TryHeal(Player player, int amount, out int healedHP, out bool isMaxHP)
    {
        healedHP = player.GetHP() + amount;  // out으로 hp 값 계산
        isMaxHP = healedHP >= 100;  // 예시로 HP가 100 이상이면 MaxHP로 설정
    }
}

 

public class Item : MonoBehaviour
{
    public Player player;

    private void Function(Player player)
    {
        int healedHP;      // 첫 번째 out 변수
        bool isMaxHP;      // 두 번째 out 변수

        // TryHeal 메서드를 호출하여 여러 값을 반환
        Manager.Instance.TryHeal(player, 20, out healedHP, out isMaxHP);

        Debug.Log("Healed HP: " + healedHP);       // healedHP 출력
        Debug.Log("Is Max HP: " + isMaxHP);       // MaxHP 여부 출력
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        Function(player);
    }
}

 

이런식으로 TryHeal 에서 매개변수를 여러개를 반환하고 싶을 때 out을 사용하게 된다. ( 값을 할당해야만 사용 가능)

 

[in]

읽기 전용 참조를 사용하지만 class를 전달할 때는 주소가 복사 되기 때문에 성능의 이점은 없다. 하지만 구조체는 다르다.

 

데미지 정보 처리 방식 정리

게임에서는 여러 가지 데미지 정보를 처리해야 하는데, 이를 효율적으로 관리하려면 **구조체(struct)**를 사용하는 것이 성능 측면에서 유리합니다. struct를 사용하면 값 타입으로 데이터가 관리되므로, 메모리 할당을 최소화하고 GC 발생을 줄이는 데 유리합니다.

1. 데미지 클래스 또는 구조체 설계

  • 클래스(class)를 사용할 경우, 인스턴스를 생성할 때마다 힙에 할당되므로 GC 발생에 영향을 미칩니다.
  • 구조체(struct)를 사용할 경우, 값 타입이기 때문에 스택 메모리에 할당되며 GC 영향을 받지 않게 됩니다.

2. 데미지 정보 예시

게임에서 데미지 정보는 보통 다음과 같은 값들로 구성됩니다:

  • 기본 데미지(Base Damage): 무기나 캐릭터의 기본 공격력
  • 추가 데미지(Additional Damage): 버프나 아이템 효과로 추가되는 데미지
  • 피해 유형(Damage Type): 물리적 피해, 마법 피해 등
  • 상태 효과(Status Effects): 추가적인 상태 변화, 예를 들어 출혈,
  • 크리티컬 확률(Critical Chance): 크리티컬 히트가 발생할 확률
  • 크리티컬 배율(Critical Multiplier): 크리티컬 히트가 발생했을 때 데미지 배율

3. 구조체를 사용한 데미지 정보 처리

 

// 데미지 정보를 나타내는 구조체
public struct DamageInfo
{
    public int BaseDamage;
    public int AdditionalDamage;
    public DamageType Type;
    public bool IsCritical;
    public float CriticalMultiplier;

    public DamageInfo(int baseDamage, int additionalDamage, DamageType type, bool isCritical, float criticalMultiplier)
    {
        BaseDamage = baseDamage;
        AdditionalDamage = additionalDamage;
        Type = type;
        IsCritical = isCritical;
        CriticalMultiplier = criticalMultiplier;
    }

    public int CalculateTotalDamage()
    {
        int totalDamage = BaseDamage + AdditionalDamage;

        if (IsCritical)
        {
            totalDamage = Mathf.CeilToInt(totalDamage * CriticalMultiplier);
        }

        return totalDamage;
    }
}

// 데미지 유형을 나타내는 enum
public enum DamageType
{
    Physical,
    Magical,
    True
}

 

4. 데미지 계산 예시

 

public class CombatManager : MonoBehaviour
{
    public int CalculateDamage(DamageInfo damageInfo)
    {
        return damageInfo.CalculateTotalDamage();
    }
    
    public void ApplyDamage(Enemy enemy, DamageInfo damageInfo)
    {
        int totalDamage = CalculateDamage(damageInfo);
        enemy.TakeDamage(totalDamage);
    }
}

 

데미지 처리 다이어그램

다이어그램을 통해 데미지 정보 처리 흐름을 시각적으로 이해할 수 있도록 그려볼게요.

  1. DamageInfo 구조체에 데미지 정보가 담기고,
  2. CalculateTotalDamage() 메서드를 통해 총 데미지가 계산됩니다.
  3. 계산된 총 데미지가 적에게 적용됩니다.

 

+------------------------+
|   DamageInfo        |  
|--------------------------|
| BaseDamage        |
| AdditionalDamage |
| DamageType         |
| IsCritical                |
| CriticalMultiplier     |
+-------------------------+
          |
          v
+-------------------------------------------+
| CalculateTotalDamage()             |
|--------------------------------------------|
| TotalDamage = BaseDamage +  |
| AdditionalDamage                       |
| If Critical, apply multiplier           |
+-------------------------------------------+
          |
          v
+------------------------+
| ApplyDamage       |
|-------------------------|
| Apply calculated   |
| damage to enemy |
+------------------------+

 

 

성능 최적화 포인트

  1. 구조체 사용: 데미지 정보처럼 작고 간단한 데이터는 구조체로 관리하는 것이 메모리 사용과 성능에 유리합니다.
  2. GC 방지: struct는 값 타입이라 GC 부담을 줄여줍니다. 매번 데미지 계산 시마다 힙 메모리에서 객체를 할당하지 않으므로 성능이 향상됩니다.
  3. 이해하기 쉬운 코드: DamageInfo 구조체를 사용하면 데미지 처리 로직이 한 곳에 모여 있어 코드 관리가 용이하고, 확장성이 뛰어납니다.
728x90

'유니티 > Performance' 카테고리의 다른 글

Unity - 자식 클래스 참조 비교  (0) 2025.03.20
Unity - GetComponent 위치에 따른 퍼포먼스  (0) 2025.03.20
Unity - Json 비교  (0) 2025.03.20
유니티 Dictionary 키 찾기 비교  (0) 2025.01.31