본문 바로가기

Unity

[Unity] 네이버 지도 Dynamic Map API로 마커와 정보 창 구현하기

네이버 지도 Dynamic Map API 활용 개요

이번 포스팅에서 다룰 주제는 Dynamic Map API 활용입니다. 이번 포스팅은 Unity에서 Static Map API를 활용하여 네이버 지도 API를 구현했던 이전 포스팅에 이어 작성됩니다. 이전 포스팅에서는 정적 지도 이미지를 Unity 프로젝트에 통합하는 방법과 기본적인 네이버 지도 API 활용법을 다뤘다면, 이번에는 한 단계 더 나아가 Dynamic Map API를 활용해 동적인 지도와 실시간 상호작용을 구현하는 과정을 소개합니다.

 

Unity 환경에서 Dynamic Map API를 활용하려면, 지도의 동적 렌더링과 사용자 상호작용을 처리하기 위해 WebView를 사용해야 합니다. WebView는 HTML과 JavaScript 기반으로 동작하며, 이를 통해 네이버 지도 Dynamic Map API를 Unity에 통합하고 Unity와 지도 간 데이터를 주고받을 수 있습니다.

이번 프로젝트의 핵심은 동적으로 지도를 구현하고 사용자 입력에 따라 마커를 추가하며, 각 마커에 정보를 표시하는 기능을 구현하는 것입니다. 이러한 기능은 특히 사용자와의 상호작용이 중요한 애플리케이션 개발, 예를 들어 모바일 게임, 지도 기반 서비스 등 다양한 Unity 프로젝트에서 활용될 수 있습니다.

이 글에서는 Dynamic Map API의 주요 기능과 Unity에서 이를 활용하는 방법, 그리고 WebView를 통해 실시간 마커 추가와 정보 창 표시를 구현하는 과정을 다룰 예정입니다. Unity와 외부 API를 연동한 실제 사례를 통해 동적 지도 구현의 원리와 적용 방법을 배울 수 있을 것입니다.

이 프로젝트는 다음과 같은 주요 기능을 포함합니다:

  • 동적 맵 초기화: Unity 프로젝트 내에서 동적으로 렌더링되는 네이버 지도를 설정하고 초기화합니다.
  • 사용자 입력을 통한 마커 추가: 사용자가 입력한 정보를 기반으로 지도에 새로운 마커를 동적으로 생성합니다.
  • 마커 클릭 시 정보 창 표시: 생성된 마커를 클릭하면 해당 마커와 관련된 정보를 정보 창으로 표시하여 사용자와 상호작용을 강화합니다.

Dynamic Map API란?

네이버 클라우드 플랫폼에서 제공하는 Dynamic Map API는 웹과 모바일 애플리케이션에서 상호작용 가능한 지도를 구현할 수 있도록 지원하는 서비스입니다. 이 API를 통해 개발자는 사용자 인터페이스에 부드럽고 반응성 높은 지도를 통합할 수 있습니다.

이전에 사용했던 정적 지도(Static Map)는 미리 생성된 이미지 형태의 지도로, 사용자와의 상호작용이 제한적입니다. 반면, Dynamic Map은 사용자의 입력에 따라 실시간으로 지도 내용을 업데이트하며, 확대/축소, 이동 등 다양한 상호작용을 지원합니다. 이를 통해 사용자 경험을 향상시킬 수 있습니다.

Dynamic Map API의 주요 특징은 다음과 같습니다:

  • 동적 렌더링: 사용자의 조작에 따라 지도가 실시간으로 업데이트되어 부드러운 상호작용을 제공합니다.
  • 실시간 마커 및 정보 창 추가: 개발자는 지도 위에 마커를 동적으로 추가하고, 각 마커에 대한 정보 창을 표시하여 사용자에게 풍부한 정보를 전달할 수 있습니다.
  • 다양한 사용자 정의 옵션: 지도 스타일, 마커 디자인, 정보 창 레이아웃 등 다양한 요소를 커스터마이징하여 애플리케이션의 요구에 맞게 지도를 구성할 수 있습니다.

NAVER Maps API v3

NAVER Maps API v3로 여러분의 지도를 만들어 보세요. 유용한 기술문서와 다양한 예제 코드를 제공합니다.

GPM WebView 설명 및 설치 방법

네이버 클라우드 플랫폼에서는 Unity용 Naver Map SDK를 별도로 제공하지 않습니다. 따라서 Unity에서 Dynamic Map을 활용하려면 WebView를 설치하고, 해당 WebView에 Web Dynamic Map API를 호출하는 방식을 사용해야 합니다. Unity Asset Store에서 WebView를 검색하면 주로 유료 플러그인이 많이 등장하며, 가격도 부담스러운 경우가 많습니다. 다행히 NHN에서 무료로 제공하는 GPM에 WebView 기능이 포함되어 있어 이를 활용할 수 있습니다. 이번 포스트에서는 GPM의 WebView를 이용해 Dynamic Map을 구현하는 방법을 살펴보겠습니다.

 

안드로이드 플랫폼 설정

GPM의 WebView는 Android와 iOS 환경에서만 동작하며, 이번 포스트에서는 Android를 기반으로 진행하겠습니다.

먼저 Unity의 플랫폼을 Android로 변경해야 합니다. 이를 위해 Unity 에디터 상단 메뉴에서 File > Build Settings를 클릭한 뒤, Platform 목록에서 Android를 선택하고 Switch Platform 버튼을 눌러 플랫폼을 전환해줍니다. 플랫폼 전환 후, GPM에서 요구하는 템플릿 설정을 해야 합니다. 설정 방법은 다음과 같습니다.

dependencies {
    // 필수 구성 요소
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72'
    
    // ShowSafeBrowsing API를 사용할 경우 추가
    implementation 'androidx.browser:browser:1.3.0'
}

**ADDITIONAL_PROPERTIES**
android.useAndroidX=true

GPM 다운로드 및 설치

Unity Asset Store에서 GPM을 다운로드받아 프로젝트에 Import 해줍시다. 다운로드 받은 후,

  • Unity 에디터의 Window > Package Manager에서 My Assets 탭으로 이동하여 GPM 패키지를 선택합니다.
  • Download 버튼을 클릭해 다운로드한 후, Import 버튼을 눌러 프로젝트에 패키지를 추가합니다.
  • Import 설정 창에서 기본값을 유지한 상태로 Import를 클릭하면, 프로젝트의 Assets 디렉토리에 GPM 관련 폴더가 생성됩니다.

GPM을 Import하면 Unity 에디터 좌측 상단 메뉴 툴바에 Tools라는 탭이 추가됩니다. Tools > GPM > Manager를 클릭하면 Game Package Manager가 열립니다. 이곳에서 서비스 목록 중 WebView를 선택하여 설치를 완료하면 됩니다.

안드로이드 AndroidManifest.xml 에서 인터넷 사용 설정 추가

Project Settings > Player > Publishing Settings > Build > Custom Main Manifest 에 체크하여 Custom Manifest를 생성합니다. 위에서 생성한 것들과 함께 모든 커스텀 빌드 파일들은 Assets > Plugins > Android 폴더에 생성됩니다.

이후, <manifest> 블록 안에, 그리고 <application> 블록 위에 다음과 같이 기입해줍니다.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  • INTERNET: 네트워크를 통한 데이터 전송과 수신에 필요.
  • ACCESS_NETWORK_STATE: 네트워크 연결 상태를 확인하기 위해 필요.
  • ACCESS_FINE_LOCATION: GPS 기반의 정확한 위치 정보를 얻기 위해 필요.

INTERNET 권한만으로도 정상적으로 동작할 가능성이 높지만, 저는 안정성을 위해 세 가지 권한을 모두 부여하였습니다. 만약 아래 두 권한(ACCESS_NETWORK_STATE와 ACCESS_FINE_LOCATION)의 필요성이 잘 와닿지 않는다면, 우선 INTERNET 권한만 부여하고 진행하셔도 무방합니다.

HTML 및 JavaScript 코드 설명

HTML 및 JavaScript 코드를 스니펫 형태로 정리하고, 각각의 수행 역할을 자세히 설명드리겠습니다. 전체 코드는 이후에 별도로 다룰 예정입니다.

지도 초기화 및 HTML 구조

HTML 구조는 다음과 같습니다:

  1. <script> 태그: 네이버 지도 API 스크립트를 로드합니다.
    • YOUR_CLIENT_ID를 사용자의 네이버 클라우드 플랫폼에서 발급받은 Client ID로 교체해야 합니다.
  2. CSS 스타일링: 전체 페이지를 지도에 할당하며, 지도의 크기를 설정합니다.
  3. JavaScript 초기화: window.onload 이벤트에서 initMap()을 호출하여 지도를 초기화합니다.

HTML 구조 코드

<html>
  <head>
    <script src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=YOUR_CLIENT_ID"></script>
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
      }
      #map {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
  </body>
</html>
  • 지도 API 스크립트: YOUR_CLIENT_ID는 필수이며, 올바르지 않은 경우 지도가 표시되지 않습니다.
  • CSS: 페이지의 여백과 크기를 제거하여 지도가 전체 화면을 차지하도록 설정합니다.
  • #map: 지도가 표시될 HTML 요소입니다.

네이버 클라이언트 아이디 발급을 아직 하지 못했다면

네이버 클라우드 플랫폼에서 인증 키를 발급받는 과정은 이전 포스트에서 상세히 다뤘으니, 해당 내용을 참고해 주시기 바랍니다.

 

JavaScript에서 지도 생성(초기화)

initMap()은 네이버 지도 객체를 생성하고 초기 마커를 추가합니다.

function initMap() {
  var cityhall = new naver.maps.LatLng(37.5666805, 126.9784147);

  map = new naver.maps.Map("map", {
    center: cityhall,
    zoom: 15,
    mapTypeControl: true,
    gestureHandling: "cooperative",
  });

  console.log("지도 초기화 완료");

  // 초기 마커 추가
  addMarkerWithInfoWindow(
    37.5666805,
    126.9784147,
    "서울특별시청",
    "서울특별시 중구 태평로1가 31"
  );
}
  • 지도 객체 생성: naver.maps.Map은 지도를 초기화합니다.
    • center: 지도 중심 좌표.
    • zoom: 초기 확대 수준.
  • 초기 마커 추가: addMarkerWithInfoWindow를 호출하여 초기 마커를 추가합니다.

초기 마커는 테스트용으로 사용되며, 지도의 초기화 지점을 표시하는 역할을 합니다. 따라서 필요에 따라 초기 마커를 제거하고 사용해도 무방합니다.

동적 마커 추가 - addMarkerWithInfoWindow 함수

이 함수는 특정 좌표에 마커를 추가하고, 마커 클릭 시 정보 창을 표시합니다.

function addMarkerWithInfoWindow(lat, lng, title, description) {
  var marker = new naver.maps.Marker({
    position: new naver.maps.LatLng(lat, lng),
    map: map,
    clickable: true,
    zIndex: 100,
  });

  var contentString = `
    <div style="width:250px; text-align:center; padding:10px;">
      <h4>${title}</h4>
      <p>${description}</p>
    </div>
  `;

  var infowindow = new naver.maps.InfoWindow({
    content: contentString,
    disableAnchor: true,
    zIndex: 101,
  });

  naver.maps.Event.addListener(marker, "click", function () {
    if (infowindow.getMap()) {
      infowindow.close();
    } else {
      infowindow.open(map, marker);
    }
  });

  markers.push({ marker: marker, infowindow: infowindow });
}
  • 마커 생성: naver.maps.Marker를 사용해 특정 좌표에 마커를 추가.
  • 정보 창 생성: HTML 콘텐츠를 포함하는 naver.maps.InfoWindow를 생성.
  • 클릭 이벤트: 마커 클릭 시 정보 창 열기/닫기 이벤트를 등록.
  • 마커 배열 관리: markers 배열에 추가하여 마커를 추적 및 관리.

마커 정보 창에 관한 추가 팁

위에서 설명한 infowindow 생성 및 내용 작성 방법은 Naver Map API의 규격을 따릅니다. 네이버 클라우드 플랫폼에서는 API 예제를 별도로 정리해두었으므로, 추가적인 기능이 필요하다면 해당 링크를 참고하여 학습하시기를 권장드립니다.

NAVER Maps API v3

NAVER Maps API v3로 여러분의 지도를 만들어 보세요. 유용한 기술문서와 다양한 예제 코드를 제공합니다.

모든 마커 제거 - removeAllMarkers 함수

모든 마커를 지도에서 제거하고 배열을 초기화합니다.

function removeAllMarkers() {
  for (var i = 0; i < markers.length; i++) {
    var markerData = markers[i];
    markerData.marker.setMap(null);
    markerData.infowindow.close();
  }
  markers = [];
  console.log("모든 마커 제거 완료");
}
  • 모든 마커 제거: setMap(null)을 호출하여 지도에서 마커를 제거.
  • 정보 창 닫기: 각 마커에 연결된 정보 창도 닫음.
  • 배열 초기화: markers 배열을 초기화하여 상태를 리셋.

Unity와의 통신 - receiveMessageFromUnity 함수

Unity에서 전달된 메시지를 기반으로 동작을 수행합니다.

function receiveMessageFromUnity(message) {
  console.log("Unity로부터 메시지 수신: " + message);

  try {
    var data = JSON.parse(message);

    if (data.type === "addMarker") {
      addMarkerWithInfoWindow(
        data.latitude,
        data.longitude,
        data.title,
        data.description
      );
    } else if (data.type === "removeAllMarkers") {
      removeAllMarkers();
    } else {
      console.log("알 수 없는 메시지 타입: " + data.type);
    }
  } catch (error) {
    console.error("Unity 메시지 파싱 오류: ", error);
  }
}
  • JSON 메시지 파싱: Unity에서 전달받은 JSON 형식의 데이터를 처리.
  • 마커 추가: addMarkerWithInfoWindow를 호출하여 새로운 마커를 지도에 추가.
  • 모든 마커 제거: removeAllMarkers를 호출하여 마커를 초기화.

Unity Script - NaverMapWebView.cs

이 스크립트는 Unity에서 NHN GPM WebView API를 사용해 네이버 지도 HTML 페이지를 띄우고, 사용자가 WebView에서 발생시키는 인터랙션(예: 마커 클릭, 정보 창 열기/닫기)을 Unity에서 처리할 수 있도록 작성된 코드입니다. WebView의 설정부터 실행, 그리고 발생하는 이벤트를 처리하는 과정까지 포함되어 있습니다.

네임스페이스 선언

using Gpm.WebView; // GPM WebView 라이브러리 사용
using UnityEngine; // Unity 관련 클래스 및 함수 사용
using System.Collections.Generic; // List와 같은 컬렉션 클래스 사용
  • Gpm.WebView: GPM WebView API를 사용하기 위한 네임스페이스
  • UnityEngine: Unity에서 기본 제공하는 함수와 클래스를 사용하기 위해 필요
  • System.Collections.Generic: 리스트 등 컬렉션 클래스 사용을 위해 포함

상수 및 변수 선언

private const string NaverMapURL = "https://your_domain.com/naver_map.html";
private bool isWebViewOpen = false;
  • NaverMapURL: WebView에서 불러올 네이버 지도 HTML 파일의 URL을 나타냄
    • 개인 서버가 반드시 필요. 또는 웹에 해당 HTML을 게시하여 접근할 수 있어야 함.
    • your_domain 자리에 자신이 HTML을 게시한 사이트 링크를 입력해주시면 됩니다.
  • isWebViewOpen: WebView가 열려 있는지 여부를 확인하는 플래그로 중복 실행을 방지

HTML 파일을 서버에 올려서 사용하는 이유

네이버 지도 API를 활용하기 위해 HTML 파일을 개인 서버에 업로드해서 사용하는 이유는 API와의 통신 방식 때문입니다. 처음에는 Unity의 StreamingAssets 폴더에 HTML 파일을 넣어 로컬에서 불러오는 방식을 시도했지만, 이 방법으로는 네이버 지도 API와의 통신이 불가능해 API를 정상적으로 사용할 수 없었습니다. 이는 로컬 파일은 웹 서버를 거치지 않기 때문에 네이버 지도 API에서 요구하는 올바른 HTTP 통신이 이루어지지 않기 때문입니다.

따라서 HTML 파일을 개인 서버에 업로드해 외부에서 접근 가능하도록 설정해야 합니다. 개인 서버는 여러 방법으로 구축할 수 있습니다. 저는 제 개인 서버를 사용하여 구현하였으나, AWS EC2를 활용하면 1년간 무료로 사용할 수 있는 서버를 간단하게 만들 수 있습니다. AWS EC2를 사용하는 방법은 추후 포스팅에서 자세히 다룰 예정입니다. 이렇게 서버를 활용하면 네이버 지도 API와의 통신이 원활히 이루어져 API 기능을 정상적으로 사용할 수 있습니다.

Start 메서드

void Start()
{
    ShowWebView(); // WebView를 표시하는 메서드 호출
}
  • Unity 객체가 활성화될 때 호출
  • 초기 실행 시 WebView를 띄우는 ShowWebView 메서드를 호출

ShowWebView 메서드

public void ShowWebView()
{
    if (isWebViewOpen)
    {
        Debug.Log("WebView already open."); // 이미 WebView가 열려 있음을 로그로 출력
        return;
    }

    var configuration = new GpmWebViewRequest.Configuration
    {
        style = GpmWebViewStyle.POPUP,
        isNavigationBarVisible = false,
        isCloseButtonVisible = true,
        backgroundColor = "#FFFFFF",
        position = new GpmWebViewRequest.Position
        {
            hasValue = true,
            x = 0,
            y = 0
        },
        size = new GpmWebViewRequest.Size
        {
            hasValue = true,
            width = Screen.width,
            height = Screen.height / 2
        }
    };

    var schemeList = new List<string> { "customscheme" };
    GpmWebView.ShowUrl(NaverMapURL, configuration, OnCallback, schemeList);

    isWebViewOpen = true;
    Debug.Log("WebView opened.");
}
  • WebView가 이미 열려 있는지 확인하고 중복 실행을 방지
  • GpmWebViewRequest.Configuration 객체를 사용하여 WebView의 스타일, 크기, 배경색 등을 설정
  • customscheme과 같은 커스텀 스킴을 설정
  • ShowUrl 메서드를 통해 WebView 실행
  • 실행 후 isWebViewOpen 플래그를 업데이트하여 상태 기록

GPM WebView 사용을 위한 추가 팁

이번 포스트에서는 팝업 형식의 WebView를 불러오는 기능만 구현하였지만, GPM WebView는 이 외에도 다양한 기능을 지원합니다. 예를 들어 네비게이션 바 표시 여부, 커스텀 스킴 처리, 웹뷰 내 자바스크립트 실행 등 다양한 확장 기능을 활용할 수 있습니다. 이러한 기능에 대해 더 학습하고 싶으시다면 아래 링크를 통해 자세히 살펴보시길 추천드립니다.

 

OnCallback 메서드

private void OnCallback(GpmWebViewCallback.CallbackType callbackType, string data, GpmWebViewError error)
{
    if (error != null)
    {
        Debug.LogError($"WebView Error: {error.domain}, Code: {error.code}, Message: {error.message}");
        isWebViewOpen = false;
        return;
    }

    if (callbackType == GpmWebViewCallback.CallbackType.Scheme)
    {
        if (data.StartsWith("customscheme://"))
        {
            string message = data.Substring("customscheme://".Length);
            HandleMessageFromWebView(message);
        }
    }
}
  • WebView에서 발생하는 이벤트와 오류를 처리
  • 오류가 발생하면 로그를 출력하고 isWebViewOpen 상태를 초기화
  • 커스텀 스킴 이벤트 발생 시 데이터를 처리하고 HandleMessageFromWebView 메서드로 메시지를 전달

HandleMessageFromWebView 메서드

private void HandleMessageFromWebView(string message)
{
    Debug.Log($"Received message from WebView: {message}");

    if (message.StartsWith("markerClicked:"))
    {
        string markerName = message.Substring("markerClicked:".Length);
        Debug.Log($"Marker clicked: {markerName}");
    }
    else if (message == "infoWindowOpened")
    {
        Debug.Log("Info window opened.");
    }
    else if (message == "infoWindowClosed")
    {
        Debug.Log("Info window closed.");
    }
    else if (message.StartsWith("mapEvent:"))
    {
        Debug.Log($"Map Event: {message}");
    }
}
  • WebView에서 전달된 메시지를 처리
  • 받은 메시지를 로그에 출력
  • 마커 클릭 이벤트를 처리하고, 클릭된 마커 이름을 로그에 출력
  • 정보 창 열림/닫힘 이벤트 처리
  • 지도와 관련된 기타 이벤트를 처리하며 메시지를 로그로 출력

Unity Script - NaverDynamicMapMarker.cs

이 스크립트는 Unity와 GPM WebView를 사용하여 지도에 동적으로 마커를 추가하거나 제거하는 기능을 제공합니다. 사용자가 입력한 지역 정보와 설명을 바탕으로 네이버 지도에 마커를 표시하며, 필요한 경우 모든 마커를 삭제할 수도 있습니다. 아래는 주요 기능과 코드의 역할을 설명한 내용입니다.

초기화 및 이벤트 연결

private void Start()
{
    addMarkerButton.onClick.AddListener(OnAddMarkerButtonClicked);
    removeMarkersButton.onClick.AddListener(RemoveAllMarkers);
}
  • Start 메서드에서 버튼 클릭 이벤트를 연결합니다.
  • addMarkerButton 클릭 시 마커 추가 메서드를 호출하며, removeMarkersButton 클릭 시 모든 마커를 삭제합니다.

사용자가 입력한 지역 정보를 바탕으로 Geocode API 호출

private void OnAddMarkerButtonClicked()
{
    string location = locationInput.text;
    string description = descriptionInput.text;

    if (string.IsNullOrEmpty(location))
    {
        Debug.LogError("지역 이름을 입력해주세요.");
        return;
    }

    StartCoroutine(GeocodeManager.Instance.GetGeocode(location, (addresses) =>
    {
        if (addresses != null && addresses.Count > 0)
        {
            Address firstResult = addresses[0];
            float latitude = float.Parse(firstResult.y);
            float longitude = float.Parse(firstResult.x);

            if (string.IsNullOrEmpty(description))
            {
                description = location;
            }

            AddMarkerToMap(latitude, longitude, location, description);
        }
        else
        {
            Debug.LogError("해당 지역을 찾을 수 없습니다.");
        }
    }));
}
  • 사용자가 입력한 지역 이름으로 Geocode API를 호출해 좌표를 검색합니다.
  • 좌표 변환이 성공하면 지도에 마커를 추가합니다.
  • 설명이 비어 있는 경우 지역 이름을 설명으로 사용합니다.

지도에 마커 추가

private void AddMarkerToMap(float latitude, float longitude, string title, string description)
{
    string message = $"{{\"type\":\"addMarker\",\"latitude\":{latitude},\"longitude\":{longitude},\"title\":\"{title}\",\"description\":\"{description}\"}}";
    GpmWebView.ExecuteJavaScript($"receiveMessageFromUnity('{message}');");
    Debug.Log($"Marker added: {title} at ({latitude}, {longitude})");
}
  • ExecuteJavaScript 메서드를 사용해 WebView 내의 JavaScript 함수에 메시지를 전달합니다.
  • 메시지에는 마커의 위도, 경도, 제목, 설명 등의 정보를 포함합니다.

모든 마커 삭제

public void RemoveAllMarkers()
{
    GpmWebView.ExecuteJavaScript("removeAllMarkers();");
    Debug.Log("Remove all markers request sent");
}
  • JavaScript 명령어를 호출해 지도 상의 모든 마커를 제거합니다.
  • Unity 디버그 로그를 통해 명령어가 실행되었음을 확인합니다.

전체 코드 정리

HTML / JavaScript

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Naver Map - Dynamic Map API</title>
    <script
      type="text/javascript"
      src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=YOUR_CLIENT_ID"
    ></script>
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
      }
      #map {
        width: 100%;
        height: 100%;
      }
    </style>
    <script>
      var map; // 지도 객체
      var markers = []; // 마커 배열

      // 지도 초기화 함수
      function initMap() {
        // 지도 중심 좌표 설정 (서울시청)
        var cityhall = new naver.maps.LatLng(37.5666805, 126.9784147);

        // 지도 생성
        map = new naver.maps.Map("map", {
          center: cityhall,
          zoom: 15,
          mapTypeControl: true,
          gestureHandling: "cooperative", // 터치 및 클릭 허용
        });

        console.log("지도 초기화 완료");

        // 초기 마커 추가
        addMarkerWithInfoWindow(
          37.5666805,
          126.9784147,
          "서울특별시청",
          "서울특별시 중구 태평로1가 31"
        );
      }

      // 마커 및 정보 창 추가 함수
      function addMarkerWithInfoWindow(lat, lng, title, description) {
        // 새로운 마커 생성
        var marker = new naver.maps.Marker({
          position: new naver.maps.LatLng(lat, lng),
          map: map,
          clickable: true, // 클릭 가능
          zIndex: 100,
        });

        console.log(`마커 생성 완료: ${title}`);

        // 정보 창 콘텐츠 생성
        var contentString = `
          <div style="width:250px; text-align:center; padding:10px;">
            <h4>${title}</h4>
            <p>${description}</p>
          </div>
        `;

        // 정보 창 생성
        var infowindow = new naver.maps.InfoWindow({
          content: contentString,
          disableAnchor: true,
          zIndex: 101,
        });

        // 마커 클릭 이벤트 리스너 등록
        naver.maps.Event.addListener(marker, "click", function () {
          if (infowindow.getMap()) {
            infowindow.close();
            console.log(`정보 창 닫힘: ${title}`);
          } else {
            infowindow.open(map, marker);
            console.log(`정보 창 열림: ${title}`);
          }
        });

        // 마커를 markers 배열에 저장
        markers.push({ marker: marker, infowindow: infowindow });
        console.log(`마커 배열에 저장: ${title}`);
      }

      // 모든 마커 및 정보 창 제거 함수
      function removeAllMarkers() {
        for (var i = 0; i < markers.length; i++) {
          var markerData = markers[i];
          markerData.marker.setMap(null); // 지도에서 마커 제거
          markerData.infowindow.close(); // 정보 창 닫기
        }
        markers = []; // 배열 초기화
        console.log("모든 마커 제거 완료");
      }

      // Unity에서 메시지 받기 (예: 동적 마커 추가)
      function receiveMessageFromUnity(message) {
        console.log("Unity로부터 메시지 수신: " + message);

        try {
          var data = JSON.parse(message); // 메시지를 JSON으로 파싱
          console.log("파싱된 데이터: ", data);

          if (data.type === "addMarker") {
            addMarkerWithInfoWindow(
              data.latitude,
              data.longitude,
              data.title,
              data.description
            );
          } else if (data.type === "removeAllMarkers") {
            removeAllMarkers();
          } else {
            console.log("알 수 없는 메시지 타입: " + data.type);
          }
        } catch (error) {
          console.error("Unity 메시지 파싱 오류: ", error);
        }
      }

      // 페이지 로드 시 지도 초기화
      window.onload = function () {
        initMap();
      };
    </script>
  </head>
  <body>
    <div id="map"></div>
  </body>
</html>
  • YOUR_CLIENT_ID 에는 자신이 직접 발급받은 클라이언트 아이디를 사용해야 합니다.

Unity - NaverMapWebView.cs

using Gpm.WebView; // GPM WebView 라이브러리를 사용하기 위한 네임스페이스
using UnityEngine; // UnityEngine 관련 클래스 및 함수 사용
using System.Collections.Generic; // List와 같은 컬렉션 클래스를 사용하기 위한 네임스페이스

/// <summary>
/// Unity에서 GPM WebView를 사용하여 네이버 지도를 표시하는 클래스
/// </summary>
public class NaverMapWebView : MonoBehaviour
{
    // 네이버 지도 HTML 파일이 호스팅된 URL
    private const string NaverMapURL = "https://your_domain.com/naver_map.html";

    // WebView가 열려 있는지 여부를 나타내는 플래그
    private bool isWebViewOpen = false;

    /// <summary>
    /// Unity 시작 시 WebView를 표시
    /// </summary>
    void Start()
    {
        ShowWebView(); // WebView를 표시하는 메서드 호출
    }

    /// <summary>
    /// WebView를 표시하는 메서드
    /// </summary>
    public void ShowWebView()
    {
        // 이미 WebView가 열려 있으면 아무 작업도 하지 않음
        if (isWebViewOpen)
        {
            Debug.Log("WebView already open."); // 디버그 로그 출력
            return;
        }

        // WebView의 설정을 정의
        var configuration = new GpmWebViewRequest.Configuration
        {
            style = GpmWebViewStyle.POPUP, // WebView 스타일을 팝업으로 설정
            isNavigationBarVisible = false, // 네비게이션 바 숨김
            isCloseButtonVisible = true, // 닫기 버튼 표시
            backgroundColor = "#FFFFFF", // 배경색 설정
            position = new GpmWebViewRequest.Position
            {
                hasValue = true, // 위치 값을 설정
                x = 0, // 화면의 x 좌표
                y = 0 // 화면의 y 좌표
            },
            size = new GpmWebViewRequest.Size
            {
                hasValue = true, // 크기 값을 설정
                width = Screen.width, // 화면 너비에 맞춤
                height = Screen.height / 2 // 화면 높이의 절반으로 설정
            }
        };

        // WebView에서 사용할 커스텀 URL 스킴 목록
        var schemeList = new List<string> { "customscheme" };

        // WebView를 지정된 URL로 열기
        GpmWebView.ShowUrl(NaverMapURL, configuration, OnCallback, schemeList);

        // WebView가 열려 있다고 플래그 설정
        isWebViewOpen = true;

        Debug.Log("WebView opened."); // 디버그 로그 출력
    }

    /// <summary>
    /// WebView에서 발생하는 콜백 처리
    /// </summary>
    private void OnCallback(GpmWebViewCallback.CallbackType callbackType, string data, GpmWebViewError error)
    {
        // 에러가 발생한 경우 처리
        if (error != null)
        {
            Debug.LogError($"WebView Error: {error.domain}, Code: {error.code}, Message: {error.message}");
            isWebViewOpen = false; // WebView 상태 플래그 초기화
            return;
        }

        // 스킴 이벤트 처리
        if (callbackType == GpmWebViewCallback.CallbackType.Scheme)
        {
            // 커스텀 스킴으로 시작하는 데이터인지 확인
            if (data.StartsWith("customscheme://"))
            {
                // 스킴 접두사를 제거하여 메시지 추출
                string message = data.Substring("customscheme://".Length);
                HandleMessageFromWebView(message); // WebView 메시지 처리 메서드 호출
            }
        }
    }

    /// <summary>
    /// WebView에서 받은 메시지를 처리하는 메서드
    /// </summary>
    /// <param name="message">WebView에서 전달된 메시지</param>
    private void HandleMessageFromWebView(string message)
    {
        Debug.Log($"Received message from WebView: {message}"); // 받은 메시지를 디버그 로그로 출력

        // 특정 마커 클릭 이벤트 처리
        if (message.StartsWith("markerClicked:"))
        {
            string markerName = message.Substring("markerClicked:".Length); // 마커 이름 추출
            Debug.Log($"Marker clicked: {markerName}"); // 마커 클릭 로그 출력
        }
        // 정보 창 열림 이벤트 처리
        else if (message == "infoWindowOpened")
        {
            Debug.Log("Info window opened."); // 정보 창 열림 로그 출력
        }
        // 정보 창 닫힘 이벤트 처리
        else if (message == "infoWindowClosed")
        {
            Debug.Log("Info window closed."); // 정보 창 닫힘 로그 출력
        }
        // 기타 지도 이벤트 처리
        else if (message.StartsWith("mapEvent:"))
        {
            Debug.Log($"Map Event: {message}"); // 지도 이벤트 로그 출력
        }
    }
}

Unity - NaverDynamicMapMarker.cs

using UnityEngine; // UnityEngine 관련 클래스 및 함수를 사용하기 위한 네임스페이스
using UnityEngine.UI; // UI 관련 클래스 사용
using TMPro; // TextMeshPro 관련 클래스 사용
using Gpm.WebView; // GPM WebView 관련 클래스 사용

/// <summary>
/// Unity에서 사용자가 입력한 지역 정보를 바탕으로 지도에 마커를 추가하는 매니저 클래스
/// </summary>
public class NaverDynamicMapMarker : MonoBehaviour
{
    // 지역 이름을 입력받기 위한 TextMeshPro UI 필드
    public TMP_InputField locationInput;

    // 지역에 대한 설명을 입력받기 위한 TextMeshPro UI 필드
    public TMP_InputField descriptionInput;

    // 마커를 추가하는 버튼
    public Button addMarkerButton;

    // 마커를 모두 삭제하는 버튼
    public Button removeMarkersButton;

    /// <summary>
    /// 초기화 메서드로, Unity의 Start 이벤트에서 호출됨
    /// </summary>
    private void Start()
    {
        // 버튼 클릭 이벤트에 OnAddMarkerButtonClicked 메서드를 연결
        addMarkerButton.onClick.AddListener(OnAddMarkerButtonClicked);

        // 버튼 클릭 이벤트에 RemoveAllMarkers 메서드를 연결
        removeMarkersButton.onClick.AddListener(RemoveAllMarkers);
    }

    /// <summary>
    /// 마커 추가 버튼 클릭 시 호출되는 메서드
    /// </summary>
    private void OnAddMarkerButtonClicked()
    {
        // 사용자가 입력한 지역 이름을 가져옴
        string location = locationInput.text;

        // 사용자가 입력한 설명을 가져옴
        string description = descriptionInput.text;

        // 지역 이름이 비어 있는 경우 에러 로그 출력 후 종료
        if (string.IsNullOrEmpty(location))
        {
            Debug.LogError("지역 이름을 입력해주세요."); // 디버그 로그 출력
            return;
        }

        // Geocode API를 호출하여 지역 이름에 해당하는 좌표를 검색
        StartCoroutine(GeocodeManager.Instance.GetGeocode(location, (addresses) =>
        {
            // 검색 결과가 존재할 경우
            if (addresses != null && addresses.Count > 0)
            {
                // 첫 번째 검색 결과를 사용
                Address firstResult = addresses[0];

                // 좌표를 문자열에서 float로 변환
                float latitude = float.Parse(firstResult.y);
                float longitude = float.Parse(firstResult.x);

                // 설명이 비어 있을 경우 지역 이름을 설명으로 사용
                if (string.IsNullOrEmpty(description))
                {
                    description = location;
                }

                // 지도에 마커 추가
                AddMarkerToMap(latitude, longitude, location, description);
            }
            else
            {
                // 검색 결과가 없을 경우 에러 로그 출력
                Debug.LogError("해당 지역을 찾을 수 없습니다.");
            }
        }));
    }

    /// <summary>
    /// 지도에 마커를 추가하는 메서드
    /// </summary>
    /// <param name="latitude">마커의 위도</param>
    /// <param name="longitude">마커의 경도</param>
    /// <param name="title">마커의 제목</param>
    /// <param name="description">마커의 설명</param>
    private void AddMarkerToMap(float latitude, float longitude, string title, string description)
    {
        // JavaScript 함수 호출을 위한 메시지 생성
        string message = $"{{\"type\":\"addMarker\",\"latitude\":{latitude},\"longitude\":{longitude},\"title\":\"{title}\",\"description\":\"{description}\"}}";

        // WebView에서 JavaScript 함수 실행
        GpmWebView.ExecuteJavaScript($"receiveMessageFromUnity('{message}');");

        // 디버그 로그로 마커 추가 정보를 출력
        Debug.Log($"Marker added: {title} at ({latitude}, {longitude})");
    }

    /// <summary>
    /// 지도에서 모든 마커를 제거하는 요청을 JavaScript로 전달합니다.
    /// </summary>
    public void RemoveAllMarkers()
    {
        // GpmWebView.ExecuteJavaScript를 통해 Unity에서 WebView로 JavaScript 명령을 실행합니다.
        // "removeAllMarkers();" 명령어는 JavaScript 환경에서 해당 함수를 호출합니다.
        GpmWebView.ExecuteJavaScript("removeAllMarkers();");

        // Unity 디버그 콘솔에 로그 메시지를 출력하여 함수 호출 여부를 확인합니다.
        Debug.Log("Remove all markers request sent");
    }
}

Unity - NaverMapAPI.cs

주소 검색을 위해 사용하는 GeocodeManager 스크립트를 실행하려면 Client IDClient Secret이 필요합니다. 이 스크립트는 네이버 클라우드 플랫폼 API를 Unity에서 전역적으로 활용할 수 있도록 구현한 것입니다.

using UnityEngine;

public class NaverMapAPI : MonoBehaviour
{
    // 싱글톤 인스턴스를 위한 정적 프로퍼티
    public static NaverMapAPI Instance { get; private set; }

    // 네이버 지도 API의 지오코드 요청 URL    
    [HideInInspector] public string geocodeApiUrl = "https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode";
    // 네이버 지도 API의 정적 지도 요청 URL
    [HideInInspector] public string mapStaticApiUrl = "https://naveropenapi.apigw.ntruss.com/map-static/v2/raster";
    // 네이버 클라우드 플랫폼에서 발급받은 클라이언트 아이디
    [HideInInspector] public string clientID = "YOUR_CLIENT_ID";
    // 네이버 클라우드 플랫폼에서 발급받은 클라이언트 시크릿
    [HideInInspector] public string clientSecret = "YOUR_CLIENT_SECRET";

    // Awake 메서드는 유니티 라이프 사이클 중 객체가 처음 생성될 때 호출됨
    private void Awake()
    {
        // 싱글톤 인스턴스 설정
        if (Instance == null)
        {
            // 현재 인스턴스를 설정하고 다른 씬 로드 시 파괴되지 않도록 설정
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            // 이미 인스턴스가 존재하면 현재 객체를 파괴
            Destroy(gameObject);
        }
    }
}

Unity - GeocodeManager.cs

아래는 주소 검색에 사용되는 GeocodeManager 스크립트입니다. 이 스크립트 역시 이전 포스트에서 이미 자세히 설명되었으므로, GeocodeManager의 상세한 구현 및 설정 방법이 궁금하시다면 [Unity] 유니티에서 네이버 지도 API 사용하기 포스트를 참고해 주시길 바랍니다. 해당 포스트를 통해 설정 과정과 사용법을 더욱 자세히 확인하실 수 있습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using Newtonsoft.Json;

public class GeocodeManager : MonoBehaviour
{
    // 싱글톤 인스턴스를 위한 정적 프로퍼티
    public static GeocodeManager Instance { get; private set; }

    private void Awake()
    {
        // 싱글톤 인스턴스 설정
        if (Instance == null)
        {
            // 현재 인스턴스를 설정하고 다른 씬 로드 시 파괴되지 않도록 설정
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            // 이미 인스턴스가 존재하면 현재 객체를 파괴
            Destroy(gameObject);
        }
    }

    // 주소를 지오코딩하여 좌표 정보를 가져오는 코루틴
    public IEnumerator GetGeocode(string query, System.Action<List<Address>> callback)
    {
        // 지오코드 API URL 생성 (query 매개변수를 URL 인코딩)
        string url = $"{NaverMapAPI.Instance.geocodeApiUrl}?query={UnityWebRequest.EscapeURL(query)}";

        // UnityWebRequest 객체를 사용하여 GET 요청 생성
        UnityWebRequest request = UnityWebRequest.Get(url);
        // 요청 헤더에 클라이언트 ID와 시크릿 설정
        request.SetRequestHeader("X-NCP-APIGW-API-KEY-ID", NaverMapAPI.Instance.clientID);
        request.SetRequestHeader("X-NCP-APIGW-API-KEY", NaverMapAPI.Instance.clientSecret);

        // 요청 전송 및 응답 대기
        yield return request.SendWebRequest();

        // 네트워크 오류 또는 프로토콜 오류가 발생한 경우
        if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
        {
            // 오류 메시지를 출력하고 콜백으로 null 전달
            Debug.LogError(request.error);
            callback(null);
        }
        else
        {
            // 응답이 성공적인 경우 응답 본문을 문자열로 가져옴
            string jsonResponse = request.downloadHandler.text;
            // 응답 문자열을 파싱하여 주소 리스트로 변환
            List<Address> addresses = ParseGeocodeResponse(jsonResponse);
            // 콜백 함수 호출하여 주소 리스트 전달
            callback(addresses);
        }
    }

    // JSON 응답 문자열을 Address 객체 리스트로 변환하는 메서드
    private List<Address> ParseGeocodeResponse(string jsonResponse)
    {
        // JSON 문자열을 GeocodeResponse 객체로 디시리얼라이즈
        GeocodeResponse geocodeResponse = JsonConvert.DeserializeObject<GeocodeResponse>(jsonResponse);
        // GeocodeResponse 객체에서 주소 리스트를 반환
        return geocodeResponse.addresses;
    }
}

// Geocode API 응답 구조체
public class GeocodeResponse
{
    // JSON 응답에서 addresses 필드를 매핑
    public List<Address> addresses { get; set; }
}

// 주소 정보를 담는 클래스
public class Address
{
    // 도로명 주소
    public string roadAddress { get; set; }
    // 지번 주소
    public string jibunAddress { get; set; }
    // 영어 주소
    public string englishAddress { get; set; }
    // 경도
    public string x { get; set; }
    // 위도
    public string y { get; set; }
}

Unity에서의 실제 구현

이 프로젝트는 WebView를 사용하기 위해 안드로이드 모바일 기반 앱으로 제작되었습니다. 따라서 이 포스트에서 다루는 전체적인 레이아웃은 제가 사용 중인 Z Flip 3의 해상도를 기준으로 작성되었습니다. 만약 다른 기종을 사용하신다면, 해당 기기의 해상도와 화면 비율에 맞게 UI를 조정해 주시길 바랍니다. 프로젝트의 레이아웃은 유연하게 수정될 수 있도록 설계하는 것이 중요합니다.

Dynamic Map API 구현을 위한 새로운 Scene 생성

기존 Static Map 구현과 충돌하지 않게, 새로운 Scene을 생성하여 진행합니다.

전체 레이아웃

저는 WebView를 화면 상단 절반에 띄우고, 나머지 밑 부분에 UI를 구현하였습니다. 웹뷰의 크기를 변경하고 싶으시다면, NaverMapWebView.cs 파일에서 아래 부분을 취향에 맞게 수정하시면 됩니다.

size = new GpmWebViewRequest.Size
{
    hasValue = true, // 크기 값을 설정
    width = Screen.width, // 화면 너비에 맞춤
    height = Screen.height / 2 // 화면 높이의 절반으로 설정
}

여기서 width와 height 값을 조정하여 웹뷰의 크기를 자유롭게 설정할 수 있습니다. 사용자의 기기 해상도와 레이아웃 요구사항에 따라 적절한 값을 입력해 주세요.

Manager Object에 Script 추가

Manager 오브젝트에는 다음 스크립트가 모두 추가되어 있습니다.

  • NaverMapAPI
  • GeocodeManager
  • NaverMapWebView
  • NaverDynamicMapMarker.

이번 프로젝트는 기능 시연을 목적으로 한 간단한 프로젝트이기에 이렇게 구현되었지만, 실제 프로젝트에서는 유지보수를 고려해 스크립트를 체계적으로 설계하고 관리하는 것이 중요합니다. 각 스크립트의 역할을 명확히 분리하고, 필요한 경우 적절한 구조를 설계하여 프로젝트가 확장되더라도 쉽게 관리할 수 있도록 해야 합니다.

NaverDynamicMapMarker의 직렬화 필드에는 위 스크린샷과 같이 InputFieldButton을 연결해 줍시다.

또한, 버튼의 OnClick 이벤트 추가NaverDynamicMapMarker.cs에서 스크립트로 이미 구현되어 있으므로, Unity 인스펙터에서 별도로 버튼 클릭 이벤트를 설정할 필요는 없습니다. 필드에 적절히 UI 요소를 연결하기만 하면 됩니다.

전체 코드의 동작 흐름

초기화 단계:

  • Unity에서 GPM WebView를 통해 HTML 파일을 로드합니다.
  • HTML 파일에서 initMap() 함수가 호출되어 네이버 지도 객체를 생성하고 초기 마커를 추가합니다.

사용자 입력 처리:

  • Unity의 UI를 통해 사용자가 지역 이름과 설명을 입력하고 버튼을 클릭합니다.
  • Unity는 Geocode API를 호출하여 지역 이름으로 해당 좌표를 검색합니다.

마커 추가:

  • Geocode API에서 반환된 좌표를 기반으로 Unity는 JavaScript의 addMarkerWithInfoWindow 함수를 호출합니다.
  • JavaScript는 해당 좌표에 마커를 추가하고, 클릭 시 정보 창을 표시하는 이벤트를 등록합니다.

마커 관리:

  • Unity에서 모든 마커 제거 명령을 전달하면, JavaScript의 removeAllMarkers 함수가 실행됩니다.
  • 이 함수들은 지도에서 마커를 제거하고 상태를 초기화합니다.

통합된 시스템 동작:

  • Unity, HTML, JavaScript가 상호작용하여 사용자가 입력한 데이터를 기반으로 동적 마커 추가 및 관리를 수행합니다.

실제 동작 화면

개발 시 발생했던 시행착오 정리

이번 프로젝트를 진행하면서 가장 크게 깨달은 점은 바로 캐싱 관련 오류의 중요성이었습니다. 초반에는 HTML 코드에 문제가 있다고 판단하여 여러 차례 수정을 시도했으며, 모바일 기반 웹뷰에서 동작을 확인해야 했기 때문에 에디터에서 바로 디버깅하지 못하고 지속적으로 빌드 후 테스트를 반복했습니다. 그러나 코드 수정에도 불구하고 문제가 개선되지 않아 많은 시행착오를 겪었습니다.

문제의 원인은 서버에 있었습니다. HTML 코드의 위치를 변경하면서 기존에 캐싱된 잘못된 HTML과 JavaScript 코드가 무효화되었고, 새로운 코드를 정상적으로 불러오게 되면서 문제가 해결되는 것을 확인할 수 있었습니다. 저는 이후 서버에서 파일을 삭제하거나 옮기는 방식으로 캐싱 문제를 다소 원시적인 방법으로 해결했지만, 이후 Unity에서 Caching.ClearCache()를 사용하거나 다른 방식으로 캐싱 문제를 해결할 수 있다는 것을 알게 되었습니다. 아직 캐싱에 대한 이해가 부족한 부분이 많아, 이와 관련된 추가적인 정보를 공유해 주신다면 큰 도움이 될 것 같습니다.

또한, 싱글톤 객체에서 발생하는 캐싱 문제도 깨닫게 되었습니다. Unity에서 Caching.ClearCache()를 수행하거나 Library 폴더를 삭제해도, 싱글톤 객체에 캐싱된 정보는 남아 있는 경우가 있었습니다. 이를 해결하려면 해당 싱글톤 컴포넌트를 Hierarchy 또는 Inspector에서 제거한 후 다시 부착해야만 정상적으로 초기화할 수 있었습니다.

캐싱 문제는 간과하기 쉬운 부분이지만, 이번 경험을 통해 디버깅 과정에서 반드시 고려해야 하는 중요한 요소라는 것을 배울 수 있었습니다. 앞으로도 캐싱 관련 오류를 보다 효율적으로 해결할 수 있는 방법을 적극적으로 탐구하고 적용해 나가고자 합니다.

결론

이번 포스트에서는 Unity와 네이버 Dynamic Map API를 연동해 동적 지도를 구현하는 방법을 다뤘습니다. Unity 내에서 WebView를 활용해 웹 기반 API와 상호작용하며 지도를 동적으로 업데이트하고, 사용자 입력에 따라 마커와 정보 창을 실시간으로 추가하는 과정을 통해 Unity와 웹의 연동 부분을 공부하는 데에도 큰 도움이 되었습니다.

개발 과정에서 가장 크게 배운 점 중 하나는 캐싱 문제의 중요성이었습니다. HTML 및 JavaScript 코드를 수정해도 반영되지 않는 문제가 발생했는데, 이는 브라우저와 서버에 캐싱된 이전 데이터 때문이었습니다. 캐싱된 데이터를 무효화하고 최신 데이터를 반영하는 방법을 배우는 과정에서, 단순한 문제 같아 보였던 캐싱이 디버깅에서 얼마나 중요한 역할을 하는지 체감할 수 있었습니다.

또한, Unity에서 싱글톤 객체와 캐싱의 관계도 중요한 교훈이었습니다. 싱글톤 객체에 남아 있는 데이터는 Unity의 일반적인 캐시 초기화 방법(Caching.ClearCache() 또는 Library 폴더 삭제)으로는 제거되지 않았습니다. 이를 해결하기 위해 싱글톤 객체를 직접 제거하거나 초기화해야 했고, 이러한 경험을 통해 싱글톤 구조와 캐싱의 특성을 더욱 깊이 이해할 수 있었습니다.

이번 프로젝트를 통해 단순히 동적 지도를 구현하는 방법뿐만 아니라, 웹 연동에서의 교훈, 캐싱 문제에 대한 대처법, Unity 구조의 이해라는 소중한 경험을 얻을 수 있었습니다. 앞으로 이 경험을 바탕으로 더 나은 프로젝트를 개발하고, 문제를 미리 예방하며 효율적인 솔루션을 제시할 수 있는 개발자로 성장해 나가고자 합니다.

이 글이 비슷한 주제를 고민하시는 분들께 작은 도움이 되길 바라며, 더 나은 해결 방법이나 의견이 있다면 언제든 함께 나눌 수 있으면 좋겠습니다. 감사합니다.


Project Github URL