[유니티/Unity] WebGL로 빌드하여 NextJS와 연동하기

·

6 min read

개요

React Unity WebGL 를 사용하여 NextJS와 Unity WebGL 연동을 구현한 간단한 예제입니다.

유니티 프로젝트 생성

  1. 프로젝트를 생성하기에 앞서 WebGL을 사용할거라서, 사용할 유니티 버전에 WebGL이 추가되지 않았다면 Add modules로 추가해줍니다.

  2. 유니티 프로젝트를 생성후 NextJS에서 데이터를 받아와 출력해줄 UI와 NextJS로 데이터를 보낼 UI를 구성해줍니다.
    구현이 번거롭다면 Github에 Sample Project를 올려두었으니 해당 프로젝트를 클론 받아 진행하셔도 됩니다.

NextJS 프로젝트 생성

  1. NextJS 프로젝트를 생성할 경로로 이동 후 Cursor 실행 (다른 IDE툴이나 편집기를 사용하셔도 됩니다)

  2. 터미널 실행 후 nextjs 프로젝트 생성을 위한 명령어 입력

    package.json 파일 생성

     npm init -y
    

    프로젝트에 React , Next.js, React DOM 최신 버전 설치

     npm install react@latest next@latest react-dom@latest
    
  3. 실행 스크립트 설정

    package.json 파일을 실행 후 scripts에 실행을 위한 스크립트 내용을 추가해줍니다.
    "dev": "next dev"

       "scripts": {
         "test": "echo \"Error: no test specified\" && exit 1",
         "dev": "next dev"
       },
    
  4. app/page.tsx 파일 작성

    아래와 같은 구조로 page.tsx파일을 생성해줍니다.
    생성 후 h1 태그에 빨간줄에 보이는데 무시해주셔도됩니다.

  5. 실행 (및 프레임워크에 필요한 파일 자동 설치)

    다시 터미널로 돌아가 아래 명령어를 입력하면 프레임워크에 필요한 파일이 설치된 이후에 실행되어집니다.

     npm run dev
    
  6. 정상적으로 실행이 되었다면 다음과 같은 내용이 출력될 것 입니다.

    ```json

    nextjs@1.0.0 dev next dev

    ▲ Next.js 15.1.5

    • Local: localhost:3000
    • Network: http:

      ✓ Starting... It looks like you're trying to use TypeScript but do not have the required package(s) installed. Installing dependencies

      If you are not trying to use TypeScript, please remove the tsconfig.json file from your package root (and any TypeScript files in your app and pages directories).

Installing devDependencies (npm):

added 5 packages, and audited 35 packages in 1s

6 packages are looking for funding run npm fund for details

found 0 vulnerabilities

We detected TypeScript in your project and created a tsconfig.json file for you. ✓ Ready in 3.8s


7. Local 주소([http://localhost:3000](http://localhost:3000))로 접속하여 다음과 같이 표시된다면 정상적으로 출력된 것 입니다.

    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1737528166815/5e0e1e9e-be98-4364-b4e3-70cb86505c30.png align="center")


# Unity 코드 작성 (NextJS to Unity)

1. NextJS에서 호출해줄 Unity C# 코드 작성

    아래는 OnNextJsToUnityButtonClicked 라는 함수명으로 호출하기 위한 코드이고 해당 스크립트는 게임씬의 오브젝트에 위치해있어야 합니다.

    ```csharp
    using TMPro;
    using UnityEngine;

    public class GameManager : MonoBehaviour
    {
        public TextMeshProUGUI nextJsToUnityText;

        public void OnNextJsToUnityButtonClicked(string text)
        {
            nextJsToUnityText.text = text;
        }
    }

Unity 코드 작성 (Unity to NextJS)

  1. Unity WebGL 빌드에서 JavaScript와의 상호 작용을 위한 jslib 파일을 추가

    파일의 이름은 중요하지 않지만 항상 Unity 프로젝트의 /Plugins/WebGL 디렉토리에 위치해야 함

/Plugins/WebGL 경로 하위로 jslib 파일을 생성해줍니다.

텍스트 편집기 또는 메모장을 통해 작성후 확장자를 jslib로 저장해줍니다.
UnityToNextJS - 유니티에서 DLLImport 시에 사용할 이름
“userInputEvent” - NextJS에서 이벤트 구독시에 사용할 이름

    mergeInto(LibraryManager.library, {
        // Unity에서 NextJs로 문자열을 보내는 함수 (유니티에서 호출시 UnityToNextJs 함수명으로 선언하여 호출)
        UnityToNextJs: function (userInput) {
            // NextJs로 문자열을 보내는 이벤트를 발생 (NextJs에서 userInputEvent 이벤트 이름으로 받음)
            window.dispatchReactUnityEvent("userInputEvent", UTF8ToString(userInput));
        }
    });

  1. 생성된 jslib 파일 확인

  2. C# 코드 작성

     using System.Runtime.InteropServices;
     using TMPro;
     using UnityEngine;
    
     public class GameManager : MonoBehaviour
     {
         [DllImport("__Internal")]
         private static extern void UnityToNextJs(string str);
    
         public TMP_InputField unityToNextJsInputField;
    
         /* 
             기존 코드
         */
    
         public void OnUnityToNextJsButtonClicked()
         {
     #if UNITY_WEBGL && !UNITY_EDITOR
             UnityToNextJs(unityToNextJsInputField.text);
     #endif
         }
     }
    

Unity 빌드

  1. Compression Format 비활성화

    브라우저 호환성 등의 문제가 발생할 수 있으니 Edit > Project Settings 를 실행후 Web 탭을 선택하여 Compression Format을 Disabled로 설정합니다. (실제 서비스시에는 압축을 활성화하고 테스트하는 것을 추천하는 듯..)

  2. File > Build Profiles 를 실행하여 Build And Run을 클릭해줍니다. (에디터가 Web 플랫폼으로 설정되어있지 않다면 switch platform 후에 진행)

  3. 저장 위치를 설정합니다.

  4. 빌드가 완료되면 다음과 같은 웹 페이지가 열리는 걸 확인할 수 있습니다.

  5. 설정해준 저장 위치(Build폴더) 하위로 다음과 같은 파일들이 생성되어 있는 것을 확인할 수 있습니다.

  6. Build 폴더를 포함하여 하위에 생성된 아래 4개의 파일을 nextjs 프로젝트의 public/ 경로로 복사합니다.
    (public 경로가 없다면 생성)

    - Build.data
    - Build.framework.js
    - Build.loader.js
    - Build.wasm

  7. 만약 유니티 프로젝트의 수정사항이 발생한다면 위 내용을 다시 반복해주세요.

React Unity WebGL 설치 및 사용

  1. 터미널을 실행하여 react-unity-webgl을 install

     npm install react-unity-webgl
    
  2. 설치가 완료된 이후 page.tsx 파일에 유니티 실행을 위한 코드를 작성

    loaderUrl, dataUrl, frameworkUrl, codeUrl에 좀전에 프로젝트에 추가해준 유니티 빌드 내용의 경로를 입력해줍니다.
    (예제에서는 NextJS에 파일을 포함하여 사용하지만, 파일들의 용량이 크기 때문에 실제 서비스시에는 CDN 등에 올려서 사용하라고 하네요..)

     "use client"
    
     import { Unity, useUnityContext } from "react-unity-webgl";
    
     export default function Home() {
       // 유니티 WebGL 빌드후에 나온 파일들을 사용하여 유니티 컨텍스트를 생성
       const { unityProvider } = useUnityContext({
         loaderUrl: "/Build/Build.loader.js",
         dataUrl: "/Build/Build.data",
         frameworkUrl: "/Build/Build.framework.js",
         codeUrl: "/Build/Build.wasm",
       });
    
       return (
         <div className="container mx-auto p-4">
           <Unity
             unityProvider={unityProvider}
             style={{ width: "100%", height: "600px" }}
           />
         </div>
       );
     }
    
  3. 실행하여 유니티 내용이 정상적으로 출력되는 것을 확인

     npm run dev
    

  4. 코드 작성

"use client"; // Next.js 13+ 에서 클라이언트 컴포넌트임을 명시

import { useState, useCallback, useEffect } from "react";
import { Unity, useUnityContext } from "react-unity-webgl"; // Unity WebGL을 React에서 사용하기 위한 라이브러리 임포트

export default function Home() {
  // Unity로부터 받은 메시지를 저장할 상태
  const [unityMessage, setUnityMessage] = useState<string>("");

  // Unity WebGL 컨텍스트 초기화
  // unityProvider: Unity 인스턴스를 제어하기 위한 프로바이더
  // sendMessage: Unity로 메시지를 전송하기 위한 함수
  // addEventListener: Unity 이벤트 리스너 등록
  // removeEventListener: Unity 이벤트 리스너 제거
  const { unityProvider, sendMessage, addEventListener, removeEventListener } =
    useUnityContext({
      loaderUrl: "/Build/Build.loader.js", // Unity WebGL 로더 스크립트
      dataUrl: "/Build/Build.data", // Unity 게임 데이터 파일
      frameworkUrl: "/Build/Build.framework.js", // Unity 프레임워크 스크립트
      codeUrl: "/Build/Build.wasm", // WebAssembly 바이너리 파일
    });

  // Unity로 메시지를 전송하는 핸들러 함수
  const handleClick = () => {
    const input = document.querySelector("input"); // 입력 필드 요소 선택
    // 입력값 유효성 검사
    // 1. input 요소가 존재하는지
    // 2. HTMLInputElement 타입인지
    // 3. 입력값이 비어있지 않은지 확인
    if (
      !input ||
      !(input instanceof HTMLInputElement) ||
      input.value.trim() === ""
    )
      return;

    // Unity의 GameManager 오브젝트의 OnNextJsToUnityButtonClicked 메서드 호출
    // input.value를 매개변수로 전달
    sendMessage("GameManager", "OnNextJsToUnityButtonClicked", input.value);
    input.value = ""; // 메시지 전송 후 입력 필드 초기화
  };

  // Unity로부터 메시지를 받았을 때 실행되는 콜백 함수
  const handleUnityMessage = useCallback((message: string) => {
    setUnityMessage(message); // 받은 메시지를 상태에 저장
  }, []);

  // Unity 이벤트 리스너 등록 및 정리
  useEffect(() => {
    // Unity의 'userInputEvent' 이벤트 구독
    addEventListener("userInputEvent", handleUnityMessage);

    // 컴포넌트 언마운트 시 이벤트 리스너 정리
    return () => removeEventListener("userInputEvent", handleUnityMessage);
  }, [addEventListener, removeEventListener, handleUnityMessage]);

  return (
    <div>
      {/* 사용자 입력 UI 섹션 */}
      <div>
        <input 
          type="text" 
          placeholder="Unity로 전송할 메시지를 입력하세요" 
          style={{ width: "700px" }}
        />
        <button 
          onClick={handleClick} 
          style={{ width: "90px" }}
        >
          메시지 전송
        </button>
      </div>

      {/* Unity WebGL 게임 렌더링 영역 */}
      <Unity
        unityProvider={unityProvider}
        style={{ width: "800px", height: "800px", marginTop: "32px" }}
      />
      {/* Unity로부터 받은 메시지 표시 */}
      <p>{unityMessage}</p>
    </div>
  );
}

트러블 슈팅

NextJS의 input 사용시 입력이 정상동작 하지 않은 이슈

이슈 내용

  • NextJS의 Input창에 영어, 숫자 입력이 정상적으로 처리되지 않고, Unity WebGL 화면의 InputField에 포커스가 되고난 이후에는 아무런 입력이 되지 않는 이슈

이슈 원인

  • Unity WebGLInput.captureAllKeyboardInput 속성으로 인해 (기본값 true) 기본적으로 모든 입력이 WebGL 캔버스로 수신되도록 처리되고 있음

이슈 해결

  • https://react-unity-webgl.dev/docs/8.x.x/api/tab-index (공식 문서에 이미 잘 나와있었네요…)

  • 유니티 프로젝트에 WebGLInput.captureAllKeyboardInput 속성 비활성화

          private void Start()
          {
      #if UNITY_WEBGL && !UNITY_EDITOR
              WebGLInput.captureAllKeyboardInput = false;
      #endif
          }
    
  • NextJS 프로젝트에서 유니티 렌더시 탭 인덱스를 설정
    (일반적으로 값이 0 이상의 tabIndex를 가지면 사용자 인터페이스의 포커스 순서에 포함 됨)

            <Unity
              unityProvider={unityProvider} tabIndex={1}
            />