Garbage Collector 🐳

Garbage Collector Cover

νŒ€λͺ…: CodeSurfers
νŒ€μ›: κΉ€ν˜„μš°, 정은채
개발 ν™˜κ²½: Unity 2019.4 URP, Visual Studio, GitLab
μ œμž‘ κΈ°κ°„: 2020.08.18 ~ 2020.09.15
YouTube: Garbage Collector Devlog


Index

1. ν”„λ‘œμ νŠΈ μ†Œκ°œ

2. ν”„λ‘œμ νŠΈ κ΅¬ν˜„


ν”„λ‘œμ νŠΈ μ†Œκ°œ

λͺ©ν‘œ

Garbage CollectorλŠ” ν•œ 달 λ™μ•ˆ μ§„ν–‰ν•œ ν”„λ‘œμ νŠΈμ΄κ³  νƒ€κ²Ÿ μ½˜ν…μΈ λ₯Ό μ •ν•˜κ³  μ΅œλŒ€ν•œ λ˜‘κ°™μ΄ λ§Œλ“œλŠ”κ±Έ λͺ©ν‘œλ‘œ ν•˜μ˜€λ‹€.

Target Game
Fig 1. Target Game.

νƒ€κ²Ÿμ€ 보트 λ ˆμ΄μ‹± κ²Œμž„μΈ β€˜λ” 크루 2β€˜μ΄λ‹€. 이 κ²Œμž„μ—μ„œ μ΅œλŒ€ν•œ 따라 ν•˜λ €κ³  ν•œ λͺ‡ κ°€μ§€ ν¬μΈνŠΈκ°€ μžˆλ‹€.

  • μž”μž”ν•œ λ°”λ‹€
  • λ¬Ό μ΄νŽ™νŠΈ
  • 톡톡 νŠ€λŠ” 보트
  • μŠ€ν”Όλ””ν•œ λŠλ‚Œ

μΆ”κ°€λ‘œ 바닀에 λ– λ‹€λ‹ˆλŠ” λΆ€μœ  μ“°λ ˆκΈ°λ₯Ό μ²­μ†Œν•΄μ„œ ν•΄μ–‘ 동물듀을 λ•μžλŠ” 아이디어도 λ„£μ—ˆλ‹€.

μŠ€ν† λ¦¬


Willy
Willy

ν–‰λ³΅ν•˜κ²Œ ν—€μ—„μΉ˜λ˜ 고래 μœŒλ¦¬λ„€ λ°”λ‹€κ°€ μ“°λ ˆκΈ°λ“€λ‘œ μ˜€μ—Όλ˜μ—ˆλ‹€.
μŠ¬νΌν•˜λŠ” 윌리λ₯Ό μœ„ν•΄ 보트λ₯Ό μš΄μ „ν•˜μ—¬ μ •ν•΄μ§„ μ‹œκ°„ 내에 μ΅œλŒ€ν•œ λ§Žμ€ λΆ€μœ  μ“°λ ˆκΈ°λ₯Ό κ±΄μ Έλ‚΄μž!

ꡬ쑰

Garbage Collector은 μ‹¬ν”Œν•œ μ•„μΌ€μ΄λ“œ κ²Œμž„μ˜ ꡬ쑰λ₯Ό κ°€μ§„λ‹€. νƒ€μž„ 아웃이 되면 기둝이 남고 λ‹€μ‹œ ν”Œλ ˆμ΄ν•˜λŠ” μ‹μœΌλ‘œ λ¦¬ν”Œλ ˆμ΄μ„±μ΄ μ§™λ‹€.

: Main Flow

μ—­ν•  λΆ„λ‹΄ 및 개발 일정

: νŒ€ ꡬ성원
κΉ€ν˜„μš° 정은채
  • ν™˜κ²½ (λ°”λ‹€, 섬)
  • Garbage의 μ›€μ§μž„
  • Game System & UI
  • 보트의 μ›€μ§μž„
  • 보트의 후크
  • μž₯μ• λ¬Ό μ›€μ§μž„
개발 일정

ν”„λ‘œμ νŠΈ κ΅¬ν˜„

1. Water System

Unity Wave
Fig 2. Unity Water System.

물을 ν‘œν˜„ν•˜κΈ° μœ„ν•΄ λ‹€μ–‘ν•œ μ‹œλ„λ₯Ό ν•΄λ³΄μ•˜λ‹€. 폴리곀 μˆ˜κ°€ λ§Žμ€ ν”Œλ ˆμΈμ„ 생성해 Vertex의 높이λ₯Ό μ‘°μ •ν•˜κ±°λ‚˜ Shader Graph λ§Œμ„ μ΄μš©ν•΄ μ›¨μ΄λΈŒλ₯Ό μƒμ„±ν–ˆλ‹€. ν•˜μ§€λ§Œ 물을 ν‘œν˜„ν•˜λŠ”κ²ƒμ€ μ–΄λ ΅κ³  정말 λ§Žμ€ μš”μ†Œλ“€μ΄ ν•„μš” ν•˜λ‹€λŠ” 것을 μ•Œκ²Œ λ˜μ—ˆλ‹€.

  • Wave : κ±°μŠ€λ„ˆ μ›¨μ΄λΈŒ
  • Reflection : λ°˜μ‚¬
  • Refraction : ꡴절
  • Caustic : λ°”λ‹₯에 μ»€μŠ€ν‹± 효과
  • Frasnel : 카메라 λ·° 각에 따라 λ°˜μ‚¬ or ꡴절
  • Scattering : μ‚°λž€ (κ°€κΉŒμš΄ λ°”λ‹€)
  • Absorption : 흑수 (λ¨Ό λ°”λ‹€) μ–΄λ‘μ›Œ λ³΄μž„

a. Mesh Base

Mesh Base Wave
Fig 3. Mesh Base Wave.

Meshλ₯Ό λŸ°νƒ€μž„ 쀑 μƒμ„±ν•΄μ„œ Octave Waveλ₯Ό μ μš©ν•˜λŠ” 방식이닀. Octave Waveλ₯Ό μ μš©ν•  λ•ŒλŠ” Mathf.PerlinNoise()의 λ°˜ν™˜ 값을 λ²„ν…μŠ€μ˜ 높이에 μ μš©ν•˜κ²Œ λ˜λŠ”λ° λ§€ ν”„λ ˆμž„ μœ„μΉ˜ κ°šμ„ λ°”κΏ”μ£Όκ³  Mesh의 노멀을 λ‹€μ‹œ 계산해 μ€˜μ•Ό ν•΄μ„œ μ—°μ‚°λŸ‰μ΄ λ§Žλ‹€. Meshκ°€ 폴리곀의 μˆ˜κ°€ 적으면 λ¬Έμ œκ°€ λ˜μ§€ μ•Šμ§€λ§Œ, 폴리곀의 수λ₯Ό 쑰금만 λŠ˜λ €λ„ ν”„λ ˆμž„ λ“œλžμ΄ λ°œμƒν•΄ λ°”λ‹€λ₯Ό ν‘œν˜„ν•˜κΈ°μ—λŠ” μ μ ˆν•˜μ§€ μ•Šμ•˜λ‹€.

b. Shader Graph Base

Wave 1 Wave 2
Wave 3 Wave 4
Fig 4. Shader Graph Base Wave.

Shader Graphλ₯Ό μ΄μš©ν•΄ 물을 ν‘œν˜„ν•˜λŠ” 방식은 λŒ€λΆ€λΆ„ λΉ„μŠ·ν–ˆλ‹€. μ„œλ‘œ λ‹€λ₯Έ λ…Έλ©€λ§΅ ν˜Ήμ€ λ…Έμ΄μ¦ˆλ§΅μ„ 컴바인 μ‹œμΌœ μ‹œκ°„μ— 흐름에 따라 offset을 μ£ΌλŠ” λ°©μ‹μœΌλ‘œ 물결을 λ§Œλ“€μ—ˆλ‹€. λ°˜μ‚¬νš¨κ³Όμ™€ μ‚°λž€ νš¨κ³Όλ„ ν‘œν˜„ν•  수 μžˆμ—ˆμœΌλ‚˜ 무언가 많이 λΆ€μ‘±ν•œ λŠλ‚Œμ΄ μžˆμ—ˆλ‹€.

c. Unity Water System

Unity Wave
Fig 5. Unity Wave.

κ²°κ΅­ λ§ˆμ§€λ§‰μ—λŠ” Unity BoatAttack μ˜€ν”ˆμ†ŒμŠ€μ˜ Water System을 μ μš©ν•˜μ˜€λ‹€. μ•žμ„œ λ§ν•œ 물을 ν‘œν˜„ν•˜κΈ° μœ„ν•œ μš”μ†Œλ“€μ€ λ‹€ λ“€μ–΄κ°€μžˆλŠ” μ•„λ¦„λ‹€μš΄ λ¬Όμ΄μ—ˆλ‹€. 물리적으둜 μΈν„°λ ‰μ…˜μ΄ λΆˆκ°€λŠ₯ν–ˆμ§€λ§Œ Buoyancy System도 λ‚΄μž₯λ˜μ–΄ μžˆμ—ˆλ‹€. 무엇보닀도 Unity Jobsystemκ³Ό Burst Compilerλ₯Ό μ΄μš©ν•΄ λͺ¨λ°”μΌμ—μ„œλ„ 높은 ν€„λ¦¬ν‹°μ˜ λ¬Ό ν‘œν˜„κ³Ό μΈν„°λ ‰μ…˜μ΄ κ°€λŠ₯ν•˜λ‹€λŠ” 게 ν₯λ―Έλ‘œμ› λ‹€.

Water Mesh
Fig 6. Water Mesh.

μ΅œμ ν™”λ₯Ό μœ„ν•΄ 원 ν˜•νƒœλ‘œ μ€‘μ•™μœΌλ‘œ 갈수둝 LODκ°€ 높은 맀쉬λ₯Ό μ‚¬μš©ν–ˆκ³  ν”Œλ ˆμ΄μ–΄λ₯Ό 계속 λ”°λΌλ‹€λ‹ˆλ©΄ ν‹°κ°€ λ‚˜κΈ° λ•Œλ¬Έμ— 일정 거리 이상 λ²Œμ–΄μ§€λ©΄ κ·Έλ•Œ ν”Œλ ˆμ΄μ–΄ μœ„μΉ˜λ‘œ μˆœκ°„μ΄λ™ μ‹œν‚¨λ‹€.

2. Procedural Terrain

Procedural Terrain
Fig 7. Procedural Terrain.

Procedural Terrain은 god like coder Sebastian Lague의 Procedural Landmass Generation의 μ‹œλ¦¬μ¦ˆμ˜ 일뢀뢄을 μ μš©ν•˜μ˜€λ‹€.

a. Height Map

Noise Map Colored
Fig 8. Height Map.

Height Map은 0(Black)κ³Ό 1(White) μ‚¬μ΄μ˜ κ°’μœΌλ‘œ 높이λ₯Ό ν‘œν˜„ν•œλ‹€. 일반적인 λ…Έμ΄μ¦ˆκ°€ μ•„λ‹ˆλΌ Perline Noiseλ₯Ό μ£Όλ©΄ μ’€ 더 organic ν•œ λŠλ‚Œμ„ μ€˜μ„œ μ‚°λ§₯μ΄λ‚˜ 물을 ν‘œν˜„ν•˜λŠ” 데 많이 μ‚¬μš©λ˜λŠ” 것 κ°™λ‹€. μƒμ„±λœ Height Map에 값에 따라 색을 λ”ν•˜λ©΄ 였λ₯Έμͺ½κ³Ό 같은 κ²°κ³Όκ°€ λ‚˜μ˜¨λ‹€.

b. Generate Mesh

Generated Mesh
Fig 9. Generated Mesh.

Mesh의 vertex λ₯Ό Height Map의 높이값에 따라 μ‘°μ •ν•˜λ©΄ μ‚°λ§₯ λŠλ‚Œμ„ λ§Œλ“€ 수 μžˆλ‹€.

3. Prototype Result

Prototype Result
Fig 10. Prototype Result.

μ—¬κΈ°κΉŒμ§€κ°€ Prototype의 κ²°κ³Όλ‹€.

4. Garbage & Spawner

Floating Garbage
Fig 11. Floating Garbage.

GarbageλŠ” ν”Œλ ˆμ΄μ–΄κ°€ Grappling Hook으둜 건져내면 μ μˆ˜κ°€ μ˜¬λΌκ°„λ‹€. λ•Œλ¬Έμ— κ·Έλƒ₯ λ‘₯λ‘₯ λ– λ‹€λ‹ˆλ©΄ λ„ˆλ¬΄ 쉽고 μž¬λ―Έκ°€ μ—†λ‹€. 쑰금의 λ¦¬ν”Œλ ˆμ΄μ„±μ„ μ£ΌκΈ° μœ„ν•΄ λ‹€μŒκ³Ό 같은 Life Cycleκ³Ό Stateλ₯Ό μ£Όμ—ˆλ‹€.

a. ꡬ쑰

Life Cycle

GarbageλŠ” Spawner에 μ˜ν•΄ 슀폰된 Spawner의 레퍼런슀λ₯Ό μ£Όμž… λ°›κ³  ν™œμ„±ν™”λœλ‹€. SpawnerλŠ” 미리 μ§€μ •λœ 수만 μžμ‹ μ˜ μ˜μ—­μ— λ¬΄μž‘μœ„ μŠ€ν°μ„ν•˜κ³  _count둜 ν™œμ„±ν™”λœ Garbage의 수λ₯Ό κ΄€λ¦¬ν•˜κ³  μžˆλ‹€. ν”Œλ ˆμ΄μ–΄κ°€ Garbageλ₯Ό 작으면 작힌 GarbageλŠ” μ°Έμ‘°ν•˜κ³  있던 Spawner의 countλ₯Ό ν•˜λ‚˜ 쀄이고 λΉ„ν™œμ„±ν™”λœλ‹€.

FSM

GarbageλŠ” ꡬ ν˜•νƒœμ˜ Detect Rangeκ°€ 있고 ν”Œλ ˆμ΄μ–΄κ°€ 거리 μ•ˆμœΌλ‘œ λ“€μ–΄μ˜€λ©΄ ν”Œλ ˆμ΄μ–΄ λ°˜λŒ€ λ°©ν–₯으둜 λ„λ§μΉœλ‹€.

b. Garbage Pool

Object Pool
Fig 12. Garbage Pool.

Garbage Pool은 λ”•μ…”λ„ˆλ¦¬μ™€ 큐둜 κ΅¬ν˜„ν•˜μ˜€λ‹€.

ObjectPool.cs
[System.Serializable]
public class Pool
{
    [SerializeField] private SpawnObjectTag tag = SpawnObjectTag.Garbage;
    [SerializeField] private List<GameObject> prefabs = null;
    [SerializeField] private int size = 0;

    public SpawnObjectTag Tag { get => tag; set => tag = value; }
    public List<GameObject> Prefabs { get => prefabs; set => prefabs = value; }
    public int Size { get => size; set => size = value; }
}

prefabsλŠ” μ—¬λŸ¬κ°€μ§€ Garbage Poolλ₯Ό λ‹΄κΈ° μœ„ν•΄ 리슀트둜 λ§Œλ“€μ—ˆλ‹€.

ObjectPool.cs
#region Initialize
private void InitVariables()
{
    poolDict = new Dictionary<string, Queue<GameObject>>();
}

private void CreatePools()
{
    foreach (Pool pool in pools)
    {
        Queue<GameObject> objectPool = new Queue<GameObject>();
        for (int i = 0; i < pool.Size; i++)
        {
            int randomIndex = Random.Range(0, pool.Prefabs.Count - 1);
            GameObject obj = Instantiate(pool.Prefabs[randomIndex]);
            obj.SetActive(false);
            objectPool.Enqueue(obj);
        }
        poolDict.Add(pool.Tag.ToString(), objectPool);
    }
}
#endregion

Garbage Pool의 생성 과정을 보면 랜덀으둜 prefabsλ¦¬μŠ€νŠΈμ—μ„œ ν•˜λ‚˜λ₯Ό 가져와 Instantiate()ν•˜κ³  λ”•μ…”λ„ˆλ¦¬ 큐에 λ‹΄λŠ”λ‹€.

b. Random Spawner

 Random Spawner
Fig 13. Random Spawner.

Random Spawnerλ₯Ό μ›ν•˜λŠ” 곳에 λ°°μΉ˜ν•˜κ³  λ²”μœ„λ₯Ό μ •ν•  수 μžˆλ‹€. λ²”μœ„λŠ” 큐브 ν˜•νƒœμ΄λ‹€.

RandomAreaSpawner.cs
private void CalculateRandomPoint()
{
    _randomRange = new Vector3(
        Random.Range(-_range.x, _range.x),
        Random.Range(-_range.y, _range.y),
        Random.Range(-_range.z, _range.z));
    _randomPoint = _origin + _randomRange;
}

_randomPointλŠ” Random Spawner의 position 값에 _randomRange offset을 λ”ν•œ μœ„μΉ˜λ‹€. _rangeλŠ” transform.localScale / 2.0f 값을 κ°€μ§„λ‹€.

RandomAreaSpawner.cs
private void RandomPointSpawn()
{
    GameObject obj2Spawn = ObjectPool.Instance.Spawn(spawnObject,
        _randomPoint, Quaternion.identity);
    obj2Spawn.GetComponent<IDependencyInjection>().Injection(gameObject);
}

Random SpawnerλŠ” Garbage Poolλ₯Ό μƒμ„±ν• λ•Œ μžμ‹ μ˜ 레퍼런슀λ₯Ό λ‹΄μ•„μ€€λ‹€.

c. Garbage

Garbage Detect Area
Fig 14. Garbage Detect Area.

Gizmos둜 λ³΄μ΄λŠ” λ²”μœ„κ°€ Garbage의 Detect Range이닀. ν”Œλ ˆμ΄μ–΄κ°€ λ²”μœ„μ•ˆμ— λ“€μ–΄μ˜€λ©΄ Navmesh Agentκ°€ ν™œμ„±ν™” 되고 ν”Œλ ˆμ΄μ–΄ λ°˜λŒ€ λ°©ν–₯으둜 λ„λ§μΉœλ‹€.

Bake Area
Fig 15. Bake Area.

Bake된 λͺ¨μŠ΅μ΄λ‹€. λ¬Ό MeshλŠ” static이 μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— static인 plane ν•˜λ‚˜λ₯Ό λ§Œλ“€μ–΄ Bakeλ₯Ό ν•˜κ³  Collider와 Mesh Rendererλ₯Ό 꺼버렸닀.

Collect Garbage
Fig 16. Collect Garbage.

ν”Œλ ˆμ΄μ–΄κ°€ Garbageλ₯Ό κ±΄μ Έλ‚΄λŠ” λŠλ‚Œμ€ 이 κ²Œμž„μ˜ 핡심이닀.

GarbageController.cs
public void Captured()
{
    _isCaptured = true;
    RB.AddForce(Vector3.up * 9000, ForceMode.Impulse);
    Instantiate(impactOnGarbage, transform.position, Quaternion.identity);
}

κ±΄μ Έλ‚΄λŠ” λŠλ‚Œμ„ 더 살리기 μœ„ν•΄ Garbageκ°€ μž‘νžˆλŠ” μˆœκ°„ Vector3.up λ°©ν–₯으둜 νŠ€μ–΄ 였λ₯΄κ²Œ ν–ˆλ‹€.

5. UI

In Game UI
Fig 17. In Game UI.

UIλŠ” 크게 4κ°€μ§€κ°€ μžˆλ‹€; Main Menu UI, In Game UI, Result UI, Transition UI.

a. Main Menu UI

Main Menu UI
Fig 18. Main Menu UI.

κ°„λ‹¨ν•œ μ‹œμž‘κ³Ό μ’…λ£Œ UI이닀.

b. In Game UI

Circle UI
Fig 19. Circle Progress UI.

Circle Progress UIλŠ” μ›λž˜ μƒμ„±λ˜λŠ” Garbage의 수 와 ν”Œλ ˆμ΄μ–΄κ°€ 건져낸 Garbage의 수λ₯Ό ν‘œν˜„ν•˜κΈ° μœ„ν•œ UI μ˜€μœΌλ‚˜ 화면을 λ„ˆλ¬΄ 많이 μž‘μ•„λ¨Ήμ–΄μ„œ νŒŒμ΄λ„ λ²„μ „μ—μ„œλŠ” λΉ μ‘Œλ‹€.

Circle H
Fig 20. Circle UI Hierarchy.

Circle Progress UIλŠ” Slider UIλ₯Ό λ‘κ°œλ₯Ό μ΄μš©ν•΄ λ§Œλ“€μ—ˆλ‹€. Slider의 μ†μž‘μ΄λŠ” κ²Œμ΄μ§€μ— μž…μ²΄κ°μ„ μ£ΌκΈ°μœ„ν•΄ λ‚©μ§ν•˜κ²Œ λ§Œλ“€μ—ˆκ³  μ„œλ‘œμ˜ μ˜μ—­μ„ μΉ¨λ²•ν•˜μ§€ μ•Šκ²Œ ν•˜κΈ° μœ„ν•΄ Maskλ₯Ό 썻닀.

Circle Glow Circle Sprite
Fig 21. Circle UI Sprites.

UI κΈ€λ‘œμš° νš¨κ³ΌλŠ” 이미지 λ‘κ°œλ₯Ό ν•©μ³μ„œ λ§Œλ“€μ—ˆλ‹€.

a. Resut UI

Result UI
Fig 22. Result UI.

Result UIλŠ” κ²Œμž„μ΄ νƒ€μž„μ•„μ›ƒ 되면 μ‚¬μš©μžμ˜ ν‚€λ³΄λ“œ 인풋을 막고 κ²°κ³Ό 창이 λœ¬λ‹€.

a. Transition UI

Transition UI
Fig 23. Transition UI.

Transistion UIλŠ” 직접 κ·Έλ €μ„œ μ‚¬μš©ν•˜μ˜€λ‹€.

LevelLoader.cs
public void LoadNextScene()
{
    transitionUI.SetActive(true);
    StartCoroutine(LoadSceneAsync(
        SceneManager.GetActiveScene().buildIndex + 1));
}

IEnumerator LoadSceneAsync(int scene)
{
    waveAnimator.SetTrigger("transition");
    yield return new WaitForSeconds(transitionTime);
    AsyncOperation operation = SceneManager.LoadSceneAsync(scene);
    while (!operation.isDone)
    {
        float progress = Mathf.Clamp01(operation.progress / .9f);
        yield return null;
    }
}

μƒˆλ‘œμš΄ 씬이 λ‘œλ“œλ λ•Œ ν˜ΈμΆœλœλ‹€.

6. Final Result

Final Result
Fig 24. Final Result.
updated_at 18-02-2021