[Unity] Mono와 IL2CPP
·
Unity,C#/Unity 정보
■ 내 C# 코드를 빌드하면 어떻게 될까?이전 글에서 유니티의 C# 코드는 스크립팅 런타임 위에서 실행된다고 했다. 스크립팅 런타임은 C# 코드를 실제로 실행하고, 메모리(GC)와 타입 정보를 관리해 주는 실행 환경이다. 이번 글에서는 1편의 계층 구조 그림에서 "바인딩 · 스크립팅 런타임" 박스를 확대해, 우리가 작성한 C# 코드가 실제로 어떻게 실행되는지 살펴보자. 1. 에디터 실행과 빌드 실행의 차이에디터에서 게임을 실행시키는 것과, 게임을 빌드해 .exe 파일을 실행하는 것은 둘 다 “C#으로 작성된 로직을 최종적으로 기계어 형태로 실행한다”라는 행위이지만, 과정은 완전히 다르다. Play 버튼을 누르면, 별도의 게임 프로그램이 실행되는 것이 아니다. Unity 에디터 자체가 C++로 만들어진 하..
[Unity] Update()는 누가 부를까? - Unity 엔진 내부
·
Unity,C#/Unity 정보
■ Unity 엔진 구조유니티에서 스크립트를 작성하거나 이미 만들어진 클래스를 살펴보면, 모두 C#으로 작성되어 있다. 그래서 유니티 엔진 자체도 C#으로 만들어졌을 것 같지만 실제로 엔진의 핵심 로직은 C++로 작성되어 있다. 우리가 사용하는 C# 클래스의 상당수는 C++ 엔진의 기능을 호출하기 위한 통로 역할을 할 뿐이다. 자주 사용하는 Rigidbody 클래스를 통해 직접 확인해 보자. 유니티는 엔진의 C# 코드를 참고용으로 공개하고 있는데(GitHub Unity-Technologies/UnityCsReference), 여기서 Rigidbody의 선언부를 단순화하면 다음과 같다.[NativeHeader("Modules/Physics/Rigidbody.h")]public partial class Ri..
[Unity] 프레임 시간 계측 툴 - Profiler
·
Unity,C#/Unity 정보
■ Unity Profiler게임을 개발하다 보면 특정 상황에서 프레임이 떨어진다거나 하는 "성능적 저하"가 발생한다. 이러한 성능 문제를 해결하기 위해선 원인을 찾아야 하는데, 이 원인을 찾도록 도와주는 것이 바로 Profiler이다.게임 성능은 결국 한 프레임을 만드는 데 몇 밀리초(ms)가 걸리는가 이다. 예를 들어 한 프레임을 만드는데 120ms가 걸린다면, 1000ms = 1s 이므로 초당 8.3 fps가 나오게 된다. 목표 프레임도 시간으로 바꿔두면 기준선이 생긴다.목표한 프레임 예산60fps16.67ms30fps33.3ms 그리고 성능 판단은 fps가 아니라 항상 ms로 한다. fps는 시간의 역수라 뺄셈이 성립하지 않기 때문이다. "8.1fps → 13.5 fps라 5.4 fps 좋아졌다"..
[Unity] 다수의 적을 어떻게 움직일까(7) - 방향장 보간 처리
·
Unity,C#/Unity 정보
■ 기존 방향장의 문제점 방향장의 경계 근처에 있는 적이 다른 적들과의 겹침 해소 보정에 밀려 옆 셀로 넘어가면, 진행 방향이 불연속으로 꺾인다. 넘어간 셀의 방향이 원래 셀로 돌아오는 성분을 가지고 있다면 적은 곧바로 복귀하고, 복귀한 뒤에는 다시 밀려 경계를 넘어간다. 이 과정이 매 프레임 반복되며, 적이 밀집하는 병목 구간에서 경계에 걸친 적들은 심하게 떨린다.이러한 일이 일어나는 근본적인 원인은 방향장이 위치에 대한 계단 함수이기 때문이다.계단 함수 : 정의역 구간별로 나누어져 각 구간에 일정한 상수값을 반환하는 함수.UpdateFlowField는 _searchDir의 8방향 중 하나를 각 셀에 할당하므로 방향은 45° 단위로 양자화되고, 인접한 두 셀의 방향이 다르다면 그 차이는 최소 45°다...
[Unity] 다수의 적을 어떻게 움직일까(6) - 필드 몹
·
Unity,C#/Unity 정보
■ 필드 몹웨이브로 몰려오는 군중 티어(3티어) 외에, 게임이 시작될 때 이미 맵 위에 놓여 있는 적을 구현한다. 웨이브만 있으면 도시 탐험의 페널티가 없기 때문이다.필드 몹은 상태를 개체가 아니라 군집이 소유하는 티어인데, 특징은 다음과 같다.개별로 행동하지 않고, 10~30마리 안팎의 소규모 군집이 하나의 상태를 공유한다. 상태를 바꾸는 주체는 군집이고, 개체는 그 변화를 언제 반영할지만 정한다.정지 ↔ 이동 전이는 목적지 도달이 아니라 타이머로 이루어진다.추격 상태로의 전이는 시야가 아니라 플레이어가 내는 소음이 일으키며, 한 번 추격에 들어가면 되돌아오지 않는다. ■ EnemyGroup군집 하나의 상태와 이동 방향을 소유하는 클래스다. 개체는 이 값을 읽어 쓸 뿐 바꾸지 않는다.EnemyGroup..
[Unity] 다수의 적을 어떻게 움직일까(5) - 적 설계
·
Unity,C#/Unity 정보
■ 적 설계 구조모든 적 객체는 Enemy 추상 클래스를 상속받아 작성한다. 적은 모두 대미지를 받을 수 있으므로 IDamageable을 구현하고, 다른 객체를 밀어내는 쪽이므로 IRepulsionSource를 구현한다.public interface IDamageable{ public void TakeDamage(float dmg);}[IDamageable.cs]여기서 적들의 티어를 나눠, 군중 티어의 적들은 단순하지만 다량으로 몰려오도록 구현하고 정예 티어의 적들은 보다 다채롭게 행동하도록 구현한다. Tier3(군중)Tier2(필드)Tier1(정예)상태가지지 않음.개체가 아닌 군집이 소유.개체가 직접 소유.조향FlowField의 조향을 따름.Chase일 때만 FlowField, Move일 때는 정..
[Unity] 다수의 적을 어떻게 움직일까(4) - 겹침 해소 · 장애물 조향
·
Unity,C#/Unity 정보
■ 겹침 해소겹침 해소는 IRepulsionReceiver를 구현한 대상만 순회한다. 장애물은 이 인터페이스를 구현하지 않으므로 애초에 이 명부에 들어 있지 않다. 밀어내기는 해도 밀려나지는 않기 때문이다.public class EnemyRegister : MonoBehaviour{ private void Update() { foreach (var self in _repulsionsReceivers) { var selfPos = self.Position; _enemyHash.Query(selfPos, _enemyBuffer); _obstacleHash.Query(selfPos, _obstacleBuffer); ..
[Unity] 다수의 적을 어떻게 움직일까(3) - 적 관리 시스템
·
Unity,C#/Unity 정보
■ 명부 등록 및 관리 시스템 - EnemyRegister앞서 만든 Flow Field와 공간 해시 그리드는 수백~수천 마리의 적을 처리하기 위한 시스템이다. 그런데 이 적들이 각자 MonoBehaviour의 Update를 가지고 스스로 움직인다면 두 가지 문제가 생긴다.순서가 보장되지 않는다.공간 해시는 "비운다 → 전원을 등록한다 → 이웃을 조회한다"의 순서로 써야 하는데, 적들이 각자의 Update에서 이 일을 하면 어떤 적은 갱신되기 전의 해시를 읽고 어떤 적은 갱신된 뒤의 해시를 읽는다.호출 비용. 적이 1000 마리면 매 프레임 1000번의 Update 호출이 일어난다. 한 곳에서 리스트를 순회하면 이것이 1번으로 줄어든다.따라서 적들을 한곳에 모아두고, 한 시스템이 정해진 순서대로 전원을 구..
[Unity] 다수의 적을 어떻게 움직일까(2) - 공간 해시 그리드
·
Unity,C#/Unity 정보
■ 공간 해시 그리드(Spatial Hash) Flow Field의 방향을 따라 이동하는 적들은 동일한 방향으로 이동하기 때문에 일렬로 겹쳐서 오게 된다.따라서 분리 및 조향을 통해 서로 겹치지 않게 처리해주어야 한다.적 하나가 주변의 다른 적을 찾으려고 전체를 훑으면, 그것을 N마리가 각자 하므로 검사 횟수가 N²으로 늘어난다. 1000마리면 프레임당 100만 번이다. 물리 엔진의 OverlapSphere를 쓰면 이 훑는 일은 물리 엔진이 대신 해주지만, 대신 적마다 콜라이더를 유지해야 하고 매 프레임 물리 엔진을 왕복해야 한다.하지만 분리에 필요한 것은 "물리적인 충돌 정보"가 아니라 "근처에 무엇이 있나"라는 위치 정보뿐이다. 물리 엔진을 거칠 이유가 없다. ■ SpatialHash 구현공간 해싱 ..