[Unity] 유니티 경량 패턴 ( Flyweight Pattern )

TNT·2024년 1월 3일
0

유니티 디자인패턴

목록 보기
12/14

이번에는 경량 패턴을 보도록 하자

먼저 개념 부터 자리잡아야하는데
보통 게임을 보다보면 같은 오브젝트를 다 돌려쓰는걸 볼수있다.
상자라던지 로그라이크 게임에서 벽모양 3d 게임에 숲에서 나무들 비슷한거가 다 돌려쓰는 경우가 많다.

실시간으로 구현 하려고할때 이러한 오브젝트 들은 다 구현해주는데
이러면 1초에 60번씩 GPU에 전달 해야하는 폴리곤들이 너무 많아진다.
그래서 보통 숲을 구현한다고 할때

줄기, 가지, 잎 형태 폴리곤 메시
나무 몸통, 잎사귀
나무 위치 포지션
각각 나무가 다르게 보이게 크기 음영 매개변수 등등
있다고 볼때
코드로 나무를 표현 하자면

개념 (나무)

Tree.cs

public class Tree
{
    Mesh mesh_;
    Texture bark_;
    Texture leaves_;
    Vector3 position_;
    double height_;
    double width_;
    Color barkTint_;
    Color leafTint_;

}

이런 개념으로 구현이 되는건데 이걸 그림으로 보자면


각각 메시 나무껍질 잎사귀 위치 매개변수 등등 가지고있는데
여기서 모두 공통된 모습 본다면
메시 나무 껍질 잎사귀 모습은 똑같이 쓸수 있고
아래에 위치 값이랑 음영 크기 매개변수로 다른 느낌나게 보이게 할수있다.
그러면 이 같이쓰는 3개 값을 묶어서 쓸수도 있지 않을까?

TreeModel.cs

public class TreeModel : MonoBehaviour
{
    Mesh mesh_;
    Texture bark_;
    Texture leaves_;
}

Tree.cs

public class Tree
{
    TreeModel model_;

    Vector3 position_;
    double height_;
    double width_;
    Color barkTint_;
    Color leafTint_;

}

이런방향으로 공통된 애들은 model로 묶어서 메시 껍질 잎사귀 이런걸 같이 두고 각자 필요한 애들은 Tree에서 구현할수도있다


이런 느낌으로 같이 공유 해서 사용한다 하지만 렌더링은 다른 이야기 인데
GPU에 보낼때 TreeModel 을 한번만 보내고 나무 그리기 들어갈때 저것만 사용해서 나무를 그릴떄 공유 데이터를 그리라고 해야한다.
요즘에 나오는 그래픽 카드 또는 API에선 이런 기능을 제공한다.
인스턴스 렌더링(instanced rendering)을 한번 보자.

사실 요부분은 최적화 부분에 해당하는거 같다.
유니티 같은 경우는

인스펙터 우측 상단에 Static 버튼이 보일텐데 Static batching이라는 개념이다.
정적 요소들을 하나의 그룹으로 만들어서 DrawCall을 줄여주는 역할을 하닌까 한번 찾아보고 활용이 가능 할꺼같은데 사용하면 좋다고 생각한다.
유니티 해당 문서 > Static batching

보통 크기가 큰 RPG 같은경우
지금은 나무 일경우 만인데 그러면 지형같은 경우는 어떻게 할수있을까?
보통 물 풀 흙 등등 다양항 지형을 연결해서 만드는데 디자이너가 전부 그릴수는 없으니
일부부만 그리고 서로 엮어서 타일 형식으로 만든다.

지형 자체를 열거형으로 만들고 월드는 격자로 관리한다.

타일 관련 데이터를 사운드나 어떤 영향을 주고 싶을때 늪지역 처럼 이동속도 느려지게 하고 싶을때 해당 지역의 정보를 얻어야할때
얻을수있는 정보를 만들어보자.

활용 (지형)

WorldMap.cs

public class WorldMap : MonoBehaviour
{
    int WIDTH = 10;
    int HEIGHT = 10;

    enum Terrain
    {
        TERRAIN_GRASS,
        TERRAIN_HILL,
        TERRAIN_RIVER
    }

    Terrain[,] tiles_;

    private void Start()
    {
        tiles_ = new Terrain[WIDTH, HEIGHT];
    }

    public int getMovementCost(int x, int y)
    {
        switch (tiles_[x, y])
        {
            case Terrain.TERRAIN_GRASS: return 1;
            case Terrain.TERRAIN_HILL: return 3;
            case Terrain.TERRAIN_RIVER: return 2;
            default: return 0;
        }
    }

    public bool isWater(int x, int y)
    {
        switch (tiles_[x, y])
        {
            case Terrain.TERRAIN_GRASS: return false;
            case Terrain.TERRAIN_HILL:return false;
            case Terrain.TERRAIN_RIVER:return true;
            default: return false;
        }
    }
}

월드맵에서 좌표를 찍어서 어디지역은 무슨 지역인지 아는 함수와
또는 위치가 물인지 확인하는 코드이다.

이렇게 쓸수도있지만 위에 하는것처럼 지역 자체를 클래스로 만들어서 한번 구현해보자.
Terrain.cs

public class Terrain : MonoBehaviour
{
    public Terrain(int moveMentCost, bool isWater,Texture texture)
    {
        moveMentCost_ = moveMentCost;
        isWater_ = isWater;
        texture_ = texture;
    }

    int moveMentCost_;
    bool isWater_;
    Texture texture_;

    public int GetMovementCost() {  return moveMentCost_; }
    public bool IsWater() { return isWater_; }
    public Texture GetTexture { get { return texture_; } }
}

이렇게 된다면 Terrain 인스턴스가 하나씩 만들어지기 때문에 이 비용은 피하고 싶다 그래서 클래에 타일 위치는 없다. 이건 월드 맵에서 관리해주자.

WorldMap.cs

public class WorldMap : MonoBehaviour
{
    int WIDTH = 10;
    int HEIGHT = 10;
    public Texture GRASS_TEXTURE;
    public Texture HILL_TEXTURE;
    public Texture RIVER_TEXTURE;


    Terrain grassTerrain_;
    Terrain hillTerrain_;
    Terrain riverTerrain_;

    Terrain[,] tiles_;

    private void Start()
    {
        grassTerrain_ = new Terrain(1, false, GRASS_TEXTURE);
        hillTerrain_ = new Terrain(1, false, HILL_TEXTURE);
        riverTerrain_ = new Terrain(1, false, RIVER_TEXTURE);
        tiles_ = new Terrain[WIDTH, HEIGHT];
        
        GenerateTerrain();
    }

    public void GenerateTerrain()
    {
        // 땅에 풀 생성
        for(int i = 0; i < WIDTH; i++)
        {
            for (int j = 0; j < HEIGHT; j++)
            {
                // 언덕 생성
                if (Random.Range(0, 10) == 0)
                    tiles_[i, j] = hillTerrain_;
                else
                    tiles_[i, j] = grassTerrain_;
            }
        }

        //강 생성
        int x = Random.Range(0, WIDTH);
        for(int y = 0; y < HEIGHT; y++)
        {
            tiles_[x, y] = riverTerrain_;
        }
    }

    public Terrain GetTile(int x, int y)
    {
        return tiles_[x, y];
    }

}

다시 작성된 코드이다
GenerateTerrain() 으로 생성하고
GetTile(x, y)로 타일 정보 가져올수있고
필요할때

int cost = worldMap.getTile(2, 3).GetMovementCost();
bool water = worldMap.getTile(2, 3).IsWater();
Texture texture = worldMap.getTile(2, 3).GetTexture();

추가로 수정해서 오브젝트 생성하고 만들고 그 안에 텍스쳐 설정하게하고 하면 사용 할수있겟다.
텍스쳐 셋팅만 해주도 유니티에서도 구동 가능하다.
사용해보고 프로파일링도 해보면서 성능에 향상된다면 사용을 한번 고려해보자.

profile
개발

0개의 댓글