Unity에서 ComfyUI API로 인게임 런타임 텍스처·아이콘을 동적 생성하는 구현법

Unity 클라이언트에서 ComfyUI API를 호출해 런타임 텍스처와 아이콘을 생성하는 흐름을 정리한다. 워크플로우 제출, WebSocket 완료 감지, 이미지 다운로드, Texture2D 적용, 캐시와 보안 경계까지 실전 구현 기준으로 다룬다.

✓ 복사 완료!
챕터머티리얼 & 텍스처
난이도중급
상태● 검토 완료
대상 엔진
Unity
최종 업데이트
Unity에서 ComfyUI API로 인게임 런타임 텍스처·아이콘을 동적 생성하는 구현법

핵심 요약

ComfyUI API 연동의 핵심은 Unity가 프롬프트를 직접 생성하는 일이 아니라 저장된 ComfyUI 워크플로우 JSON을 /prompt로 제출하고 완료된 결과만 Texture2D로 받아 UI 또는 머티리얼에 적용하는 것이다. 클라이언트는 요청 식별자와 결과 캐시를 관리하고 실제 배포에서는 ComfyUI 서버를 게임 클라이언트에 노출하지 말고 인증·큐 제어를 담당하는 백엔드를 사이에 둔다.

인게임 런타임 텍스처 생성은 언제 유효한가?

런타임 생성은 플레이어 이름, 장비 속성, 퀘스트 상태처럼 사전에 모든 조합을 구워 둘 수 없는 콘텐츠에 적합하다. 예를 들어 로그라이크의 유물 아이콘, 사용자 제작 길드 엠블럼, NPC 의뢰서 삽화에 사용할 수 있다.

반대로 전투 중 즉시 표시해야 하는 핵심 UI에는 기본 아이콘을 준비하는 편이 안전하다. Stable Diffusion 계열 생성은 GPU 큐 대기와 추론 시간이 있어 일반적인 UI 로딩보다 지연 변동이 크다.

용도런타임 생성 적합도권장 처리
플레이어 제작 엠블럼높음비동기 생성 후 CDN 또는 서버 캐시
장비 희귀도 아이콘중간결과 캐시와 기본 아이콘 동시 준비
전투 중 스킬 버튼낮음사전 제작 에셋 사용
로비 배경·의뢰서 삽화높음로딩 화면 또는 백그라운드 생성

Unity UI에 동적으로 생성한 유물 아이콘을 표시하는 예시

ComfyUI API 요청부터 Texture2D 적용까지 어떻게 구현할까?

ComfyUI 기본 API 흐름은 세 단계다. POST /prompt로 워크플로우를 큐에 넣고 WebSocket 메시지 또는 GET /history/{prompt_id}로 완료 여부를 확인한 뒤 GET /view로 출력 이미지를 다운로드한다.

1. API용 워크플로우 JSON을 준비한다

ComfyUI 편집 화면의 워크플로우는 API 형식 JSON으로 내보내야 한다. 노드 ID와 연결 구조는 유지한 채 CLIPTextEncode 노드의 text 입력만 런타임 프롬프트로 바꾼다. 아래 예시는 Unity에서 수정할 최소 형태의 요청 모델이다.

[System.Serializable]
public sealed class ComfyPromptRequest
{
    public string client_id;
    public System.Collections.Generic.Dictionary<string, object> prompt;
}

워크플로우에는 출력 파일을 만드는 SaveImage 노드가 필요하다. 생성 크기는 아이콘 목적이라면 512×512부터 시작하고 UI 표시 크기가 작아도 원본이 너무 작으면 업스케일 과정에서 윤곽이 쉽게 무너진다.

2. UnityWebRequest로 프롬프트를 제출한다

아래 코드는 API 워크플로우 템플릿의 특정 텍스트 노드를 수정해 /prompt로 전송하는 최소 예시다. workflow는 JSON을 역직렬화해 둔 노드 맵이며 네트워크 호출은 반드시 코루틴 또는 async 흐름에서 실행한다.

using System;
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

public sealed class ComfyUiClient : MonoBehaviour
{
    [SerializeField] private string comfyBaseUrl = "http://127.0.0.1:8188";

    public IEnumerator QueuePrompt(string workflowJson, string nodeId, string text, Action<string> onQueued)
    {
        var workflow = Newtonsoft.Json.Linq.JObject.Parse(workflowJson);
        workflow[nodeId]!["inputs"]!["text"] = text;

        var body = new Newtonsoft.Json.Linq.JObject
        {
            ["client_id"] = SystemInfo.deviceUniqueIdentifier,
            ["prompt"] = workflow
        }.ToString();

        using var request = new UnityWebRequest($"{comfyBaseUrl}/prompt", "POST");
        request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(body));
        request.downloadHandler = new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");
        yield return request.SendWebRequest();

        if (request.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError($"ComfyUI 큐 등록 실패: {request.error}");
            yield break;
        }

        onQueued?.Invoke(Newtonsoft.Json.Linq.JObject.Parse(request.downloadHandler.text)["prompt_id"]!.Value<string>());
    }
}

Newtonsoft.Json은 Unity 패키지 관리자의 com.unity.nuget.newtonsoft-json으로 제공된다. 문자열 치환으로 JSON을 고치면 따옴표, 줄바꿈, 프롬프트 인젝션 처리에서 쉽게 깨지므로 JSON 객체를 수정하는 편이 낫다.

flowchart LR
    A[게임 UI 요청] --> B[프롬프트 정규화]
    B --> C{캐시 적중?}
    C -- 예 --> D[Texture2D 적용]
    C -- 아니오 --> E[POST /prompt]
    E --> F[WebSocket 또는 history 완료 확인]
    F --> G[GET /view 이미지 다운로드]
    G --> H[디스크·메모리 캐시]
    H --> D

3. 완료 이미지의 바이트를 Texture2D로 변환한다

ComfyUI history 응답에서 outputs의 이미지 메타데이터를 찾는다. 파일명, 서브폴더, 타입을 쿼리 문자열로 넘겨 /view에서 PNG 또는 WebP 바이트를 가져오고 ImageConversion.LoadImage로 디코딩한다.

public IEnumerator DownloadTexture(string filename, string subfolder, string type, Action<Texture2D> onDone)
{
    var url = $"{comfyBaseUrl}/view?filename={UnityWebRequest.EscapeURL(filename)}" +
              $"&subfolder={UnityWebRequest.EscapeURL(subfolder)}&type={type}";

    using var request = UnityWebRequestTexture.GetTexture(url, false);
    yield return request.SendWebRequest();

    if (request.result != UnityWebRequest.Result.Success)
    {
        Debug.LogError($"ComfyUI 이미지 다운로드 실패: {request.error}");
        onDone?.Invoke(null);
        yield break;
    }

    onDone?.Invoke(DownloadHandlerTexture.GetContent(request));
}

UGUI의 RawImage.texture에는 Texture2D를 바로 넣을 수 있다. Image.sprite가 필요하면 Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f))로 감싼다. 머티리얼에는 MaterialPropertyBlock을 사용하면 공유 머티리얼을 복제하지 않고 텍스처를 바꿀 수 있다.

ComfyUI 완료 이미지가 Unity Texture2D와 UI Sprite로 변환되는 과정

WebSocket과 polling 중 무엇을 선택해야 할까?

프로토타입은 /history/{prompt_id} polling으로 충분하다. 운영 클라이언트는 ComfyUI의 WebSocket /ws?clientId={client_id}를 연결해 실행 완료 이벤트를 받고 최종 파일 목록만 history에서 조회하는 방식이 요청 수를 줄인다.

방식장점단점권장 상황
History polling구현이 단순함대기 중 HTTP 요청이 반복됨에디터 도구, 소규모 프로토타입
WebSocket완료 이벤트를 즉시 수신재연결과 타임아웃 처리가 필요런처, 로비, 운영 환경

Polling 간격은 0.5~1초 정도로 두고 최대 대기 시간과 취소 버튼을 제공한다. 동일 prompt_id를 여러 화면이 기다리는 경우에는 각 화면이 polling하지 말고 하나의 생성 작업 객체를 공유해야 한다.

런타임 AI 아이콘 생성의 성능과 비용은 어떻게 제어할까?

프롬프트가 아니라 결과를 캐시한다

같은 입력이 같은 결과를 보장하려면 seed, checkpoint 이름, sampler, steps, CFG, width, height를 모두 캐시 키에 포함해야 한다. 텍스트 프롬프트만 키로 쓰면 워크플로우를 바꾼 뒤 이전 이미지가 재사용될 수 있다.

  1. 프롬프트와 생성 파라미터를 정규화한다.
  2. 정규화 문자열의 SHA-256 해시를 캐시 키로 만든다.
  3. 메모리 캐시를 먼저 조회하고 없으면 디스크 또는 CDN 캐시를 조회한다.
  4. 모두 없을 때만 ComfyUI 작업을 큐에 넣는다.

메모리에는 최근 UI에서 사용하는 Texture2D만 제한 수로 보관한다. 교체한 텍스처는 더 이상 참조하지 않을 때 Destroy(texture)를 호출해야 모바일 환경에서 VRAM 누적을 피할 수 있다. 디스크 캐시는 앱 버전 또는 워크플로우 버전을 디렉터리에 포함해 무효화 범위를 좁힌다.

생성 품질과 대기 시간을 별도 정책으로 둔다

아이콘은 고해상도 생성보다 일관된 실루엣이 중요하다. 아이콘 전용 워크플로우에서 고정 seed 범위, 투명 또는 단색 배경, 짧은 네거티브 프롬프트, 낮은 steps를 정하고 결과가 부족할 때만 재생성한다. 전투 화면처럼 대기 시간이 민감한 곳에서는 생성 완료 전 기본 아이콘과 로딩 상태를 표시한다.

ComfyUI 서버를 게임 클라이언트에서 직접 호출해도 되는가?

개발 PC에서는 가능하지만 공개 빌드에서는 권장하지 않는다. ComfyUI는 일반적으로 신뢰된 내부 환경의 워크플로우 실행 서버이며 클라이언트에 주소를 넣으면 서버 주소 노출, 무단 생성 요청, 워크플로우 변조, GPU 큐 고갈 문제가 함께 생긴다.

운영 구조는 Unity 클라이언트와 ComfyUI 사이에 얇은 생성 API를 둔다. 이 API가 인증된 요청만 받고 허용된 프롬프트 파라미터를 검증하며 작업 수와 이미지 크기를 제한한 뒤 ComfyUI에 전달한다. 결과는 오브젝트 스토리지나 CDN에 저장해 이후 요청을 ComfyUI까지 보내지 않는다.

경계최소 책임
Unity 클라이언트요청 표시, 취소, 결과 적용, 로컬 캐시
생성 API인증, 파라미터 검증, 요청 제한, 작업 상태
ComfyUI승인된 워크플로우 실행
CDN·스토리지생성 파일 장기 캐시와 배포

자주 묻는 질문 (FAQ)

Q. 생성 결과를 바로 Sprite로 저장해 Addressables에 넣을 수 있는가?

런타임 빌드에서 Addressables 카탈로그를 임의로 수정하는 방식은 맞지 않는다. 생성 이미지는 persistentDataPath 또는 원격 스토리지에 저장하고 실행 중 Texture2D와 Sprite로 로드한다.

Q. ComfyUI의 출력 파일명을 그대로 클라이언트 캐시 키로 써도 되는가?

안 된다. 출력 파일명은 서버 측 실행 순서에 영향을 받을 수 있다. 입력 파라미터와 워크플로우 버전으로 만든 해시를 클라이언트와 서버의 공통 키로 사용한다.

Q. 이미지 생성 실패 시 재시도는 몇 번이 적절한가?

네트워크 오류는 지수 백오프로 1~2회 재시도할 수 있다. 워크플로우 검증 오류나 GPU 메모리 부족처럼 서버가 명확한 오류를 준 경우에는 반복 요청하지 말고 기본 에셋으로 대체하고 오류를 기록한다.

정리

Unity와 ComfyUI의 연결 자체는 /prompt 제출, 완료 확인, /view 다운로드의 단순한 비동기 파이프라인이다. 실제 품질은 이 호출 코드보다 캐시 키 설계, 기본 에셋 폴백, 서버 접근 제어, 아이콘 전용 워크플로우를 얼마나 일관되게 운영하느냐에 달려 있다.