에디터에서는 있는데 플레이어에서만 사라질 수 있습니다
Managed code stripping은 빌드에서 사용하지 않는 것으로 판단한 타입과 멤버를 제거합니다. 코드에서 직접 생성하거나 호출하는 타입은 linker가 찾기 쉽지만, 외부 설정 파일이나 서버 응답에서 받은 이름으로만 찾는 타입은 놓칠 수 있습니다. 에디터에서는 stripping을 거치지 않아 잘 되다가 IL2CPP 플레이어에서 Type.GetType이 null을 돌려주는 일이 생기는 이유입니다.
문자열을 썼다고 무조건 제거되는 것은 아닙니다. Unity linker가 알아보는 단순한 reflection 패턴도 있습니다. 문제가 되는 쪽은 빌드 시점에 실제 타입 이름을 알기 어려운 동적 경로입니다. 이때는 네임스페이스와 어셈블리를 포함한 이름을 쓰고 null도 처리합니다.
LoadTypeNameFromConfig는 파일이나 서버 설정을 읽는 프로젝트 쪽 함수입니다. 위 코드가 해당 타입을 정적으로 참조하지 않으므로 보존 설정이 필요할 수 있습니다.
using System;
string typeName = LoadTypeNameFromConfig();
// 예: Game.Runtime.EnemyFactory, Assembly-CSharp
Type type = Type.GetType(typeName);
if (type == null)
throw new InvalidOperationException($"Type not found: {typeName}");
object instance = Activator.CreateInstance(type);Preserve와 link.xml 중 하나를 좁게 적용합니다
소스 코드를 직접 수정할 수 있는 타입 하나라면 [UnityEngine.Scripting.Preserve]가 간단합니다. 여러 타입을 한곳에서 관리하거나 외부 플러그인 코드를 보존해야 하면 Assets/link.xml을 씁니다. 기본 스크립트 어셈블리에 Game.Runtime.EnemyFactory가 있다면 다음처럼 적습니다. asmdef를 사용했다면 Assembly-CSharp 대신 실제 asmdef 어셈블리 이름을 넣습니다.
[Preserve]가 이미 붙은 타입을 다시 link.xml 예제로 설명하면 원인을 구분하기 어렵습니다. 먼저 보존 설정이 없는 상태에서 빌드 증상을 확인한 뒤 둘 중 한 방법만 추가해 전후를 비교합니다.
<linker>
<assembly fullname="Assembly-CSharp">
<type fullname="Game.Runtime.EnemyFactory" preserve="all" />
</assembly>
</linker>Stripping Level은 마지막에 조정합니다
Managed Stripping Level을 낮추면 증상이 사라질 수 있지만 필요 없는 코드까지 남습니다. 먼저 사라진 타입과 실제 어셈블리 이름을 확인하고 보존 범위를 최소화합니다. 마지막으로 Development Build나 로그를 사용해 해당 reflection 경로가 IL2CPP 플레이어에서도 동작하는지 확인합니다.
공식 문서 대조
검증일
Unity 6.5 공식 문서와 API를 기준으로 동적 reflection 경로, Preserve, link.xml의 적용 범위를 대조했습니다. 모든 문자열 reflection이 제거된다고 단정하지 않고, 빌드 시점에 정적으로 확인하기 어려운 타입 이름을 다룹니다.