본문 바로가기
스파르타 내배캠

스파르타 내배캠 Unity 3기 43일차

by LemongO 2024. 2. 27.

이젠 게임보다 아트가 재밌어진다...

 

 


이제부턴 주제를 따로 정하지 않고 그날 그날 작업한 내용 중 헤맸던 부분들만 작성해야겠다.

 

 

심화주차 팀 프로젝트 -  길건너 친구들 풍 맵 생성기

MapGenerator

 

 

 

완성되어야 하는 모습이다.

 

맵 생성에는 다음과 같은 조건을 따른다.

 

와... 어지럽네;;

 

정말 복잡하지만 세 줄 요약하면 다음과 같다

  • 잔디는 밝은색, 탁한색 페어로 되어있고 순환 가능해 같은 종류도 생성 가능하다.
  • 도로는 차선 표시가 있는 연속도로는 반드시 처음과 끝이 있다.(중간도 있어 중복이 된다.)
  • 1차선 싱글 도로는 연속과 순환이 불가능해 반드시 다른 플랫폼이 와야한다.

 

해당 조건을 따르기 위해서 다음 세가지 클래스들을 생성하였다.

 

public class PlatformBase : MonoBehaviour
public class ContinuousPlatform : PlatformBase
public class SinglePlatform : PlatformBase

 

Continuous와 PlatformBase가 핵심인데 이 둘의 내부를 보자.

 

public enum PlatformType
{
    Land_01,
    LoadPair_01,
    LoadSingle,
}

public class PlatformBase : MonoBehaviour
{
    public PlatformType platformType;

    [field: SerializeField] public string Tag { get; private set; }

    public virtual void Init() { }
}

이번 로직에선 특히 string이 많이 활용되는데 그 이유는 현재 프로젝트의 PoolManager의 보관,생성 로직이

오브젝트의 Tag(string)으로 이루어지기 때문이다.

 

PlatformBase 는 enum 값을 나중에 생성시에 쓰이는 메서드에서 string으로 변환해 매개변수로 활용한다.

Tag 프로퍼티는 실제 오브젝트의 Pool에서 쓰이는 Tag이다.

 

public class ContinuousPlatform : PlatformBase
{
    [field: SerializeField] public bool IsEssential { get; private set; }
    [field: SerializeField] public bool IsCyclable { get; private set; }
    [field: SerializeField] public bool IsMid { get; private set; }
    [field: SerializeField] public bool IsLast { get; private set; }    

    [field: SerializeField] public string NextPair { get; private set; }
}

ContinuousPlatform 에서 거의 모든 분기를 관리한다.

조금 복잡하다보니 bool 값이 많아졌다.

 

IsEssential : 반드시 나의 플랫폼 다음에 나와 페어인 플랫폼이 와야하는 경우.

IsCyclable : 같은 종류가 또 나올 때, 순환이 가능하지 않으면 나와 동류는 생성하지 않게 하기 위함.

IsMid : 연속의 중간이라 중복해서 나올 수 있는지 판단하기 위함.

IsLast : 필수는 아니지만 연속이고, Last가 아니라면 다음이 올 수 있게하기 위함.

 

NextPair : 나의 다음 플랫폼 오브젝트의 Tag

 

 

맵 생성기에선 생선된 플랫폼들의 정보를 다음과 같이 저장한다.

 

public class PlatformGenerator : MonoBehaviour
{
    private Queue<PlatformBase> _platformsQueue = new Queue<PlatformBase>();
    private PlatformBase _latestPlatform;
}

 

가장 마지막에 생성된 플랫폼을 PlatformBase로 저장하는데, 다음 플랫폼 생성시에 다음과 같이 쓰인다.

 

private void GeneratePlatform()
{
    if (!IsEssentialPlatform())
        GenerateRandomPlatform();
}

 

플랫폼 생성시, 지금 생성되어야 하는 플랫폼이 마지막 플랫폼과 연관있는 플랫폼인지 확인하는

IsEssentialPlatform 메서드를 호출해 여부를 확인한다.

 

// 마지막 플랫폼이 반드시 자신의 페어가 와야하는 플랫폼인지 검사
private bool IsEssentialPlatform()
{
    ContinuousPlatform continuousPlatform = GetChildPlatform<ContinuousPlatform>();

    // 자신의 페어가 와야하면 생성 후 true 반환
    if (continuousPlatform != null && continuousPlatform.IsEssential)
    {
        // 페어가 오긴 해야하는데 중간꺼라 중복이 가능하면 확률적
        if (continuousPlatform.IsMid)
        {
            string random = Random.Range(0, 10) < 5 ? continuousPlatform.NextPair : continuousPlatform.Tag;
            GenerateEssentialPlatform(random);
            return true;
        }

        // 중간이 없는 1, 2 페어면 바로 다음 플랫폼
        GenerateEssentialPlatform(continuousPlatform.NextPair);
        return true;
    }

    return false;
}

 

IsEssentialPlatform으로 필수 플랫폼이 필요하면 

GenerateEssentialPlatform 메서드 호출로 필수 플랫폼을 생성한다.

 

private void GenerateEssentialPlatform(string platformType)
{
    DeployPlatform(platformType);
}

 

만약 false가 나와 랜덤한 플랫폼을 생성해야 하면 GenerateRandomPlatform 메서드를 호출한다.

 

public void GenerateRandomPlatform()
{
    string platformType = GetRandomTypeName();

    DeployPlatform(platformType);
}

 

둘 다 마찬가지로 DeployPlatform 메서드를 호출하지만
랜덤의 경우 GetRandomTypeName 메서드로 string을 할당 받는다.

 

private string GetRandomTypeName()
{
    // Enum으로 각 플랫폼의 1번 태그를 불러와 랜덤하게 지정        
    string randType = _platformTypes[Random.Range(0, _platformTypes.Length)];        

    if (_latestPlatform == null)
        return randType;

    ContinuousPlatform continuousPlatform = GetChildPlatform<ContinuousPlatform>();
    if (continuousPlatform != null)
    {
        // 마지막 플랫폼이 1번 플랫폼이면, 연속 플랫폼인지 확인 후 마지막이 아니면 다음 플랫폼 태그를 뽑는다.
        if (_latestPlatform.Tag == randType && continuousPlatform.IsLast == false)
            return continuousPlatform.NextPair;

        // 마지막 플랫폼의 타입이 생성될 타입과 같고, 순환이 가능하면 바로 생성한다.
        if (_latestPlatform.platformType.ToString() == randType && continuousPlatform.IsCyclable)
            return randType;
    }

    // 순환이 안 되거나, 같은 종류의 플랫폼이면 다시 뽑는다.
    if (_latestPlatform.platformType == CheckNextPlatformType(randType))
        return GetRandomTypeName();

    // 그냥 다른거
    return randType;
}

 

 

위 코드 중 주석으로 설명이 안 된 메서드 두 가지는 다음과 같다.

 

private PlatformType CheckNextPlatformType(string platformType)
{
    PlatformBase platform = ObjectPoolManager.PeekObject(platformType).GetComponent<PlatformBase>();

    return platform.platformType;
}

 

CheckNextPlatformType 메서드는 다음 플랫폼이 알기위해 랜덤으로 뽑은 태그를
PoolManager를 활용해 다음으로 올 Platform을 Peek을 통해 잠깐 꺼내보기만한다.

반환하는 타입은 Enum 이다.

 

// Continuous 또는 Single Platform을 제너릭을 활용해 반환
private T GetChildPlatform<T>() where T : PlatformBase
{
    if (_latestPlatform == null || _latestPlatform.TryGetComponent(out T continuous) == false)
        return null;

    return continuous;
}

 

GetChildPlatform 메서드는 마지막 플랫폼이 연속인지 아닌지 알기위해

실제 오브젝트에서 Continuous 또는 Single 인지 제너릭을 통해 찾아온다.

 

 

해당 메서드 중 

private string GetRandomTypeName()

private bool IsEssentialPlatform()

 

위 두 메서드를 활용해 처음 이미지를 통해 보였던 조건들을 만족시킨다.

 

그렇게하면 결과는 다음과 같다.

 

 

1차선 도로는 한 줄만 존재.

2차선 이상 도로는 처음과 끝이 존재 단, 그 다음 플랫폼은 도로가 올 수 없음.

잔디는 다음 페어가 필수가 아니지만, 연속+순환이 가능하기 때문에 한줄 또는 그 이상이 가능