내일배움캠프 50일차 TIL : StageManager 수정, out, ref 복습

woollim·2024년 12월 3일
0

내일배움캠프TIL

목록 보기
43/65
post-thumbnail

■ 개요

오늘 계획

  • 씬매니저 추가
  • 스테이지 랜덤 생성 상세 구현
    • 스테이지 종류별 데이터베이스 세분화 필요(테이블 정규화)


■ StageManager 수정

○ 스테이지 데이터베이스 정규화

  • 스테이지타입, 노말스테이지, 보스스테이지, 이벤트스테이지 테이블로 분리

○ StageManager 변경

  • 스테이지 타입 선택후 그에 맞는 몬스터 생성
/// <summary>
/// 랜덤 스테이지 생성 후 층수에 맞는 강함을 지닌 몬스터를 소환하는 함수 입니다.
/// </summary>
/// <param name="floor">탑 층수</param>
public void CreateStage(int floor = 1)
{
    StageType stageType = RandomStageType(floor);
    int stageID = 0;
    Debug.Log(stageType);

    // 스테이지 특성에 맞는 몬스터 생성
    switch (stageType)
    {
        case StageType.Boss:
            stageID = RandomStageID<BossStageData>(Managers.Data.GameDataBase.BossStageDatas);
            BossStageData bossStage = Managers.Data.GameDataBase.BossStageDatas[stageID];
            CreateMonster(bossStage._name, bossStage._monsterIDs, bossStage._monsterQuantity);
            break;
        case StageType.Normal:
            stageID = RandomStageID<NormalStageData>(Managers.Data.GameDataBase.NormalStageDatas);
            NormalStageData normalStage = Managers.Data.GameDataBase.NormalStageDatas[stageID];
            CreateMonster(normalStage._name, normalStage._monsterIDs, normalStage._monsterQuantity);
            break;
        case StageType.Event:
            // 얘는 빼도 될듯. 그라운드 생성 고려 하여 일단 두기로 결정
            break;
    }
}

/// <summary>
/// 리스트에 저장된 스테이지타입을 랜덤으로 선택하여 ID(인덱스) 값을 반환하는 함수 입니다.
/// </summary>
/// <returns>랜덤 스테이지타입 ID</returns>
private StageType RandomStageType(int floor)
{
    int stageType;

    if (floor % 10 == 0)
    {
        stageType = 0; // 보스타입 고정
    }
    else
    {
        int stageCount = Managers.Data.GameDataBase.StageTypeDatas.Count; // 스테이지 종류 갯수 반환
        stageType = Random.Range(1, stageCount); // 보스방 빼고 랜덤 선택
    }
    
    return (StageType)stageType; // 이넘으로 변환
}

/// <summary>
/// 랜덤 스테이지 ID를 반환하는 함수 입니다.
/// </summary>
/// <typeparam name="T">해당 스테이지 데이터테이블</typeparam>
/// <param name="stageMonster">스테이지 딕셔너리</param>
/// <returns>스테이지 ID</returns>
private int RandomStageID<T>(Dictionary<int, T> stageMonster)
{
    List<int> keys = new List<int>(stageMonster.Keys);
    // 딕셔너리에서 키 추출하여 리스트 만듬

    int randomIndex = Random.Range(0, keys.Count);
    // 키 리스트 카운트 범위내에서 랜덤 인덱스

    return (int)keys[randomIndex];
}

/// <summary>
/// 몬스터를 생성해주는 함수 입니다.
/// </summary>
/// <param name="stageName">스테이지 이름</param>
/// <param name="monsterIDs">몬스터 ID 리스트</param>
/// <param name="monsterQuantity">몬스터 수 리스트</param>
private void CreateMonster(string stageName, List<int> monsterIDs, List<int> monsterQuantity)
{
    Debug.Log(stageName);
    GameObject monsterPrefab;
    int listCount = monsterIDs.Count;

    for(int i = 0; i < listCount; i++)
    {
        monsterPrefab = Resources.Load<GameObject>(Managers.Data.GameDataBase.MonsterDatas[monsterIDs[i]]._prefabPath + Managers.Data.GameDataBase.MonsterDatas[monsterIDs[i]]._ID);
        if (monsterPrefab != null)
            Debug.Log("프리펩이 없습니다.");
        for (int j = 0; j < monsterQuantity[i]; j++)
        {
            Debug.Log(Managers.Data.GameDataBase.MonsterDatas[monsterIDs[i]]._name);
            Debug.Log(Managers.Data.GameDataBase.MonsterDatas[monsterIDs[i]]._prefabPath + Managers.Data.GameDataBase.MonsterDatas[monsterIDs[i]]._ID);

            //Managers.Pool.GetPool(monsterPrefab, monsterQuantity[i]);
        }
    }
}


■ out

○ 개념

  • 메서드의 매개변수로 사용되며, 해당 매개변수를 메서드 내부에서 반드시 초기화하여 반환하도록 강제하는 역할을 함
  • 주로 여러 값을 반환하거나 반환 값 외의 추가 데이터를 전달할 때 사용
  • out은 여러 값 반환이 필요한 오래된 코드베이스에서 많이 사용되었지만, 최신 코드에서는 주로 튜플이나 객체를 통해 대체되고 있음

○ 특징

  1. 초기화 필요 없음: 메서드를 호출하기 전에 out 매개변수는 초기화되지 않아도 됨
  2. 메서드 내부에서 초기화 필수: out으로 전달된 매개변수는 메서드 내부에서 반드시 값을 설정해야 함
  3. 메서드 호출 후 값 사용 가능: 메서드 호출이 끝난 후, out 매개변수에 설정된 값을 사용할 수 있습니다.

○ 사용예시

using System;

class Program
{
    static void Main()
    {
        // 변수 선언만 하고 초기화하지 않음
        int result;
        
        // out 매개변수로 메서드 호출
        Calculate(5, 3, out result);

        Console.WriteLine($"결과: {result}"); // 출력: 결과: 8
    }

    static void Calculate(int a, int b, out int sum)
    {
        // 반드시 값을 설정해야 함
        sum = a + b;
    }
}

○ 주의 사항

  • 조건문 내부에서 선언된 변수는 조건문 외부에서도 유효
    • out 변수는 조건문에서 선언되었을 때 스코프가 조건문 이후로 확장됨
    • 단, 동일한 이름의 변수를 같은 스코프에서 다시 선언할 수 없음
    • 값 초기화 주의
      • TryParse 실패 시에도 result는 기본값(숫자의 경우 0)으로 초기화됨. 이 점은 out 변수의 특징
  • 초기화 여부 확인 필요 없음
    • out 매개변수는 메서드 내부에서 무조건 초기화되므로, 메서드 호출 이후에는 안전하게 값을 사용할 수 있음
  • 다른 반환 메커니즘과 비교
    • ref: 메서드 호출 전에 초기화된 값을 전달하며, 메서드 내에서 수정 가능.
    • out: 초기화 여부와 상관없이 값을 전달하며, 메서드 내에서 반드시 초기화해야 함.
static void Example(out int value, ref int refValue)
{
    value = 10; // out 매개변수는 초기화 필수
    refValue += 10; // ref 매개변수는 초기화되어 있음
}
  • 값 반환 방식의 대안
    C# 7.0 이후부터는 튜플(Tuple)이나 ValueTuple을 사용하여 메서드에서 여러 값을 반환하는 것이 일반적
static (int sum, int product) CalculateValues(int a, int b)
{
    return (a + b, a * b);
}

var (sum, product) = CalculateValues(5, 3);
Console.WriteLine($"합: {sum}, 곱: {product}");

○ 유니티에서의 out

  • Unity에서는 여전히 out 키워드가 특정 상황에서 유용하게 사용됨

  • 특히 Unity는 C# 7.0 이후의 최신 기능을 점진적으로 받아들이고 있지만, Unity API 자체가 out을 사용하는 경우가 많아서 이 키워드를 접할 일이 있음

  • Unity에서는 여전히 많이 쓰이는 이유

    • 호환성과 성능
      Unity는 최신 C# 기능을 점진적으로 도입하지만, 여전히 기존 API와의 호환성을 유지하기 위해 out을 사용하는 경우가 많음. 특히 Unity의 네이티브 엔진 코드와의 상호작용에서 out은 효율적인 데이터 반환 방법

    • 간결함
      out을 사용하면 필요하지 않은 객체나 데이터 구조를 추가로 생성하지 않고도 값을 반환할 수 있음. 이는 메모리 할당을 줄이는 데 도움이 됨

    • 직관적 API 설계
      Unity API는 사용하기 쉽도록 설계되어 있습니다. out은 초보자도 간단히 이해할 수 있으며, 명확하게 값이 반환됨을 나타내므로 직관적임

○ 사용예시

  • Physics 관련 메서드
    • 물리 엔진에서 Rycast와 같은 메서드는 out매개변수를 사용하여 충돌 정보나 결과를 반환함
    • out RaycastHit hit: 충돌된 객체와 관련된 정보를 반환하기 위해 사용됨
    • hit 객체를 통해 충돌 위치, 충돌된 객체, 노멀 벡터 등의 정보를 얻을 수 있음
using UnityEngine;

public class RaycastExample : MonoBehaviour
{
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) // 마우스 클릭 시
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out RaycastHit hit, 100f))
            {
                Debug.Log($"Hit Object: {hit.collider.gameObject.name}");
            }
        }
    }
}
  • Try-Parse 패턴
    • Unity 스크립트에서 문자열을 숫자로 변환해야 할 때 int.TryParse나 float.TryParse를 사용하는 경우가 많음. 이 메서드들 역시 out 매개변수를 활용함
    • TryParse는 변환이 성공하면 true를 반환하며, 변환된 값을 out 매개변수를 통해 제공함
using UnityEngine;

public class ParseExample : MonoBehaviour
{
    void Start()
    {
        string input = "123";
        if (int.TryParse(input, out int result))
        {
            Debug.Log($"Parsed number: {result}");
        }
        else
        {
            Debug.Log("Parsing failed.");
        }
    }
}


■ ref

○ 개념

  • C#의 ref 키워드는 메서드의 매개변수로 전달된 변수를 참조로 전달하기 위해 사용
  • 이를 통해 메서드 내부에서 해당 변수의 값을 수정할 수 있으며, 수정된 값은 메서드 호출 이후에도 반영
  • ref는 변수의 참조를 전달함으로써 값 수정 및 성능 최적화를 가능하게 함
  • 다만, 불필요한 값 변경을 방지하기 위해 꼭 필요한 경우에만 사용하는 것이 좋음

○ 특징

  • 참조로 전달
    • 값 형식 변수도 참조로 전달되기 때문에 원본 변수가 수정됨
    • 포인터와는 다르지만, 비슷한 참조 개념을 제공함
  • 초기화 필요
    • ref 키워드를 사용하는 변수는 메서드 호출 전에 반드시 초기화되어야 함
  • 양방향 데이터 흐름
    • 메서드가 변수의 값을 읽을 수도, 수정할 수도 있음
  • out과 비교
    • out: 메서드 내부에서 반드시 초기화되어야 하며, 호출 전에 초기화되지 않아도 됨
    • ref: 호출 전에 초기화되어 있어야 하며, 메서드 내부에서 값을 읽고 수정할 수 있음

○ 사용예시

1. 기본예제

using System;

class Program
{
    static void Main()
    {
        int number = 10; // 초기화 필수
        Console.WriteLine($"Before: {number}");

        // ref를 사용하여 변수 전달
        ModifyValue(ref number);
        
        Console.WriteLine($"After: {number}");
    }

    static void ModifyValue(ref int value)
    {
        value *= 2; // 원본 변수 수정
    }
}
  • 출력
    Before: 10
    After: 20

2. 여러 값 수정

  • ref를 사용하면 여러 개의 변수를 수정할 수 있음
using System;

class Program
{
    static void Main()
    {
        int a = 5, b = 3;
        Swap(ref a, ref b);

        Console.WriteLine($"After Swap: a = {a}, b = {b}");
    }

    static void Swap(ref int x, ref int y)
    {
        int temp = x;
        x = y;
        y = temp;
    }
}
  • 출력
    After Swap: a = 3, b = 5

3. 배열과 ref

using System;

class Program
{
    static void Main()
    {
        int[] numbers = { 1, 2, 3 };
        Console.WriteLine($"Before: {string.Join(", ", numbers)}");

        ModifyArray(ref numbers);

        Console.WriteLine($"After: {string.Join(", ", numbers)}");
    }

    static void ModifyArray(ref int[] arr)
    {
        arr = new int[] { 10, 20, 30 }; // 배열 자체를 새로운 것으로 변경
    }
}
  • 출력
Before: 1, 2, 3
After: 10, 20, 30

○ 주의 사항

  • 초기화 필요
    • ref로 전달하려는 변수는 반드시 메서드 호출 이전에 초기화되어 있어야 함
  • 값 수정 여부에 주의
    • ref 매개변수는 원본 값을 수정하므로, 불필요한 값 변경을 방지하기 위해 신중히 사용해야 함
  • out과의 혼동 방지
    • ref: 메서드 호출 전에 값이 있어야 하며, 읽고 쓸 수 있음.
    • out: 메서드 호출 전에 초기화 필요 없음, 메서드 내부에서 값을 반드시 설정해야 함.

○ ref를 사용하는 상황

  • 성능 최적화: 값 형식의 큰 데이터(예: 구조체)를 복사하지 않고 참조로 전달하여 메모리 비용을 절약.
  • 상태 변경: 메서드 호출 후 변수를 직접 수정해야 하는 경우.
  • 배열 요소 수정: 배열이나 컬렉션의 특정 요소를 참조로 전달해 수정해야 할 때.

0개의 댓글