UI & Load Image

UIImage 컴포넌트

UIImage는 화면에 2D 이미지를 표시하는 기본 UI 컴포넌트입니다. Component 클래스를 상속받아 구현되어 있으며, UI 요소의 위치와 크기 정보를 관리합니다.

// UIImage.h
class UIImage : public Component
{
    using Super = Component;
public:
    UIImage();
    virtual ~UIImage();
    bool Picked(POINT screenPos);
    void SetRect(RECT rect) { _rect = rect; }
    void SetTransformAndRect(Vec2 screenPos, Vec2 size);
    void SetScreenTransformAndRect(Vec2 pos, Vec2 size, RECT rect);
    void UpdateRect(Vec2 ndcPos, Vec2 size);
    // ... 기타 메서드들 ...
private:
    Vec3 _size;       // UI 요소의 크기
    Vec3 _ndcPos;     // 정규화된 장치 좌표계(NDC)에서의 위치
    RECT _rect;       // 스크린 좌표계에서의 UI 영역
};

UI 피킹 시스템

  • 엔진의 좌표계 구조

// Graphics.h에서 정의된 크기 정보
int _viewWidth = 0;      // 게임 뷰 너비 (창의 70%)
int _viewHeight = 0;     // 게임 뷰 높이 (창의 60%)
int _projectWidth = 0;   // 전체 창 너비
int _projectHeight = 0;  // 전체 창 높이
  1. 윈도우 좌표계(스크린 좌표계):

    1. 원점(0,0)은 윈도우의 좌측 상단

    2. X축은 오른쪽으로, Y축은 아래쪽으로 증가

    3. 마우스 입력이 이 좌표계로 전달됨

  2. NDC(Normalized Device Coordinates) 좌표계:

    1. 원점(0,0)은 화면 중앙

    2. X축은 왼쪽이 -1, 오른쪽이 1

    3. Y축은 아래쪽이 -1, 위쪽이 1

    4. 3D 렌더링에 사용되는 DirectX의 기본 좌표계

  3. RECT 좌표계:

    1. Windows API의 사각형 정의 구조체

    2. left, top, right, bottom 값으로 구성

    3. 윈도우 좌표계 기준으로 작동

  • InputManager의 마우스 입력 처리

마우스 입력은 윈도우 좌표계에서 수집됩니다:

// InputManager.cpp의 마우스 위치 가져오기
POINT InputManager::GetPublicMousePos()
{
    POINT mousePos;
    ::GetCursorPos(&mousePos);           // 화면 전체에서의 마우스 위치
    ::ScreenToClient(_hwnd, &mousePos);  // 윈도우 기준 좌표로 변환
    return mousePos;
}

이 함수는 다음 단계로 동작합니다:

  1. GetCursorPos: 전체 화면에서의 마우스 위치를 가져옴

  2. ScreenToClient: 전체 화면 좌표를 창 기준 좌표로 변환

  3. 이 윈도우 좌표계 위치가 UI 피킹에 사용됨

  • NDC 좌표계 기준 UI 위치 설정

UI 요소는 NDC 좌표계를 기준으로 위치를 설정합니다:

// UIImage.cpp
void UIImage::SetTransformAndRect(Vec2 screenPos, Vec2 size)
{
    float width = GP.GetViewWidth();   // 게임 뷰 너비
    float height = GP.GetViewHeight(); // 게임 뷰 높이

    // 스크린 좌표를 NDC 좌표로 변환
    float x = screenPos.x - width / 2;    // X축 변환 (화면 중앙이 0)
    float y = height / 2 - screenPos.y;   // Y축 변환 (화면 중앙이 0, 위쪽이 양수)
    _ndcPos = Vec3(x, y, 0);
    
    // 크기 설정 (중심에서의 반지름)
    size /= 2.0f;
    _size = Vec3(size.x, size.y, 1.0f);

    // RECT 구조체 설정 (윈도우 좌표계 기준)
    _rect.left = screenPos.x - size.x;
    _rect.right = screenPos.x + size.x;
    _rect.top = screenPos.y - size.y;
    _rect.bottom = screenPos.y + size.y;
}

중요한 점은 다음과 같습니다:

  1. NDC 좌표계는 화면 중앙이 원점(0,0)

  2. 윈도우 좌표계는 좌상단이 원점(0,0)

  3. NDC는 Y축이 위쪽이 양수, 윈도우 좌표계는 아래쪽이 양수

  • 뷰포트 오프셋 보정을 통한 좌표계 매칭

UI 피킹의 핵심은 마우스 위치와 UI 요소의 RECT를 같은 좌표계에서 비교하는 것입니다:

// Graphics.cpp에서 뷰포트 설정
void Graphics::Initialize(HWND hwnd, int width, int height)
{
    // ... 생략 ...
    _viewWidth = width * (7.0f / 10.0f);      // 게임 뷰 너비 (전체 창의 70%)
    _viewHeight = height * (6.0f / 10.0f);    // 게임 뷰 높이 (전체 창의 60%)
    
    // 뷰포트 설정 - x, y 오프셋 포함
    CreateViewport(_viewWidth, _viewHeight, 
                   width * (1.0f/10.0f),    // X 오프셋 (창 너비의 10%)
                   height * (3.0f/100.0f),  // Y 오프셋 (창 높이의 3%)
                   0, 1);
}
// UIImage.cpp에서 피킹 처리
bool UIImage::Picked(POINT screenPos)
{
    // 뷰포트 오프셋 보정 (ImGui 윈도우와 게임 뷰 간의 간격)
    screenPos.x -= GP.GetProjectWidth() * (1.0f / 10.0f);   // X축 오프셋 보정
    screenPos.y -= GP.GetProjectHeight() * (3.0f / 100.0f); // Y축 오프셋 보정

    // 보정된 마우스 위치와 RECT 비교
    bool result = ::PtInRect(&_rect, screenPos);
    return result;
}

이 오프셋 보정이 중요한 이유:

  1. 게임 뷰는, 전체 창에서 일정 오프셋(X: 10%, Y: 3%)을 가진 위치에 렌더링됨

  2. 마우스 위치는 전체 창 기준으로 입력되기 때문에, 게임 뷰 좌표계로 변환하려면 오프셋을 빼야 함

  3. UI 요소의 RECT는 게임 뷰 내부 좌표계로 설정되어 있음

  4. 두 좌표계를 맞추지 않으면 마우스 클릭 위치와 실제 UI 요소 위치 간에 오차가 발생

  • 실제 피킹 과정

Scene 클래스에서 UI 피킹은, 좌표계 보정 후 Windows API의 PtInRect 함수를 사용해 충돌을 감지합니다:

// Scene.cpp
void Scene::UIPicking()
{
    // 마우스 클릭 확인
    if (INPUT.GetPublicButtonDown(KEY_TYPE::LBUTTON) == false)
        return;
    
    // 윈도우 좌표계 기준 마우스 위치 가져오기
    POINT screenPoint = INPUT.GetPublicMousePos();
    
    // 모든 UI 게임 오브젝트 순회
    for (shared_ptr<GameObject> gameObject : GetGameObjects())
    {
        // Button 컴포넌트 확인
        if (gameObject->GetComponent<Button>() != nullptr)
        {
            // 내부적으로 마우스 위치 보정 및 충돌 감지
            if (gameObject->GetComponent<Button>()->Picked(screenPoint))
            {
                // 피킹 성공 처리
                // ... 생략 ...
            }
        }
        
        // UIImage 컴포넌트 확인
        else if (gameObject->GetComponent<UIImage>() != nullptr)
        {
            // 내부적으로 마우스 위치 보정 및 충돌 감지
            if (gameObject->GetComponent<UIImage>()->Picked(screenPoint))
            {
                // 피킹 성공 처리
                picked = gameObject;
            }
        }
    }
}

이미지 로드

  • 드래그 드롭 감지 시스템

GUIManager는 Win32 API와 ImGui를 통해 외부 파일의 드래그 드롭을 감지합니다:

// GUIManager.h
class GUIManager
{
public:
    void HandleExternalFilesDrop(const filesystem::path& sourcePath);
    bool IsImageFile(const filesystem::path& path);
    void CopyFileToResourceFolder(const filesystem::path& sourcePath, const filesystem::path& destPath);
    // ... 기타 메서드들 ...
};

ImGui는 윈도우에 파일이 드롭됐을 때 이를 감지하고 이벤트를 발생시킵니다:

// GUIManager.cpp
void GUIManager::RenderUI()
{
    // ... 기타 UI 렌더링 코드 ...
    
    // 프로젝트 창 렌더링
    if (ImGui::Begin("Project", nullptr))
    {
        // 현재 선택된 폴더 표시
        RenderFolderTree(projectRoot, _selectedFolder);
        
        // 파일 그리드 표시
        RenderFileGrid(_selectedFolder);
        
        // 드래그 드롭 처리
        if (ImGui::BeginDragDropTarget())
        {
            // 외부 파일 드롭 감지
            if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("FILES"))
            {
                // 드롭된 파일 경로 추출
                const char* path = (const char*)payload->Data;
                HandleExternalFilesDrop(filesystem::path(path));
            }
            ImGui::EndDragDropTarget();
        }
    }
    ImGui::End();
    
    // ... 기타 코드 ...
}

  • 파일 형식 확인

드롭된 파일이 이미지인지 확인하는 과정:

// GUIManager.cpp
bool GUIManager::IsImageFile(const filesystem::path& path)
{
    // 파일 확장자 가져오기
    string extension = ToLower(path.extension().string());
    
    // 지원하는 이미지 형식 확인
    return (extension == ".png" || 
            extension == ".jpg" || 
            extension == ".jpeg" || 
            extension == ".bmp" || 
            extension == ".tga" || 
            extension == ".dds");
}

  • 외부 파일 처리 및 프로젝트 폴더로 복사

외부에서 드래그된 파일을 처리하는 메인 함수:

// GUIManager.cpp
void GUIManager::HandleExternalFilesDrop(const filesystem::path& sourcePath)
{
    // 폴더인지 확인
    if (filesystem::is_directory(sourcePath))
    {
        // .mesh 파일 찾기
        filesystem::path meshFile;
        vector<filesystem::path> clipFiles;

        for (const auto& entry : filesystem::directory_iterator(sourcePath))
        {
            if (entry.path().extension() == ".mesh")
            {
                meshFile = entry.path();
            }
            else if (entry.path().extension() == ".clip")
            {
                clipFiles.push_back(entry.path());
            }
        }

        // .mesh 파일이 있는 경우에만 처리
        if (!meshFile.empty())
        {
            // 폴더 이름 추출
            wstring folderName = sourcePath.filename().wstring();

            // 애니메이션 경로 구성
            vector<wstring> animations;
            for (const auto& clipFile : clipFiles)
            {
                wstring clipPath = folderName + L"/" + clipFile.stem().wstring();
                animations.push_back(clipPath);
            }

            // 모델 경로 구성
            wstring modelPath = folderName + L"/" + meshFile.stem().wstring();

            // XML 파일 생성
            filesystem::path xmlPath = L"Resource/Model/" + folderName + L"Model.xml";

            // XML 파일 작성
            RESOURCE.WriteModelToXML(
                modelPath,                    // 모델 경로
                L"AnimatedMesh_Shader",      // 셰이더 이름
                modelPath,                    // 머티리얼 경로
                folderName,                  // 모델 이름
                animations,                  // 애니메이션 목록
                xmlPath.wstring()            // XML 저장 경로
            );

            // 새로운 리소스 로드
            RESOURCE.LoadModelData(xmlPath.wstring());
        }
        return;
    }

    // 이미지 파일인지 확인
    if (!IsImageFile(sourcePath))
        return;

    // GameCoding 폴더 경로 구성 (현재 경로 사용)
    filesystem::path basePath = filesystem::current_path();

    // 대상 파일 경로 구성
    filesystem::path destPath = basePath / sourcePath.filename();

    // 파일 복사
    CopyFileToResourceFolder(sourcePath, destPath);

    // XML 생성을 위한 이름 구성 (확장자 제외)
    wstring textureName = sourcePath.stem().wstring();
    filesystem::path resourcePath = basePath / "Resource" / "Texture";
    filesystem::path xmlPath = resourcePath / (textureName + L".xml");

    // XML 파일 생성
    RESOURCE.WriteTextureToXML(
        sourcePath.filename().wstring(),  // 이미지 파일명
        textureName,                      // 텍스처 이름
        xmlPath.wstring()                 // XML 저장 경로
    );

    // 새로운 리소스만 로드
    RESOURCE.LoadResourcesByType(resourcePath, ".xml", [](const wstring& path) {
        RESOURCE.LoadTextureData(path);
    });
}

  • 파일 복사 및 변환 프로세스

파일을 프로젝트 폴더로 복사하고 필요시 변환하는 함수:

// GUIManager.cpp
void GUIManager::CopyFileToResourceFolder(const filesystem::path& sourcePath, const filesystem::path& destPath)
{
    try {
        // 대상 폴더가 없으면 생성
        filesystem::create_directories(destPath.parent_path());

        // 파일 복사
        filesystem::copy_file(sourcePath, destPath,
            filesystem::copy_options::overwrite_existing);
    }
    catch (const filesystem::filesystem_error& e) {
        // 에러 처리
        OutputDebugStringA(e.what());
    }
}

  • 리소스 로딩 및 엔진 등록

복사된 이미지 파일은 ResourceManager에 의해 텍스처 리소스로 로드됩니다:

// ResourceManager.cpp (참조용)
template<>
shared_ptr<Texture> ResourceManager::Load(const wstring& path)
{
    // 이미 로드된 텍스처인지 확인
    shared_ptr<Texture> texture = Get<Texture>(path);
    if (texture)
        return texture;
    
    // 새 텍스처 생성
    texture = make_shared<Texture>();
    if (FAILED(texture->Load(path)))
        return nullptr;
    
    // 텍스처 이름 설정 (파일명 기준)
    filesystem::path p(path);
    wstring fileName = p.filename();
    
    // 리소스 맵에 추가
    Add(fileName, texture);
    
    return texture;
}

  • 텍스처 로딩 프로세스

Texture 클래스는 DirectX를 사용하여 이미지 파일을 GPU 텍스처로 로드합니다:

// Texture.cpp 
void Texture::CreateTexture(const wstring& path)
{
	// 파일 확장자 얻기
	wstring ext = fs::path(path).extension();

	if (ext == L".dds" || ext == L".DDS")
	{
		LoadTextureFromDDS(path);
		return;
	}

	DirectX::TexMetadata md;
	DirectX::ScratchImage img;
	HRESULT hr = ::LoadFromWICFile(path.c_str(), WIC_FLAGS_FORCE_RGB, &md, img);
	CHECK(hr);

	// Generate Mipmaps
	DirectX::ScratchImage mipChain;
	hr = DirectX::GenerateMipMaps(
		img.GetImages(), img.GetImageCount(), img.GetMetadata(),
		DirectX::TEX_FILTER_DEFAULT, 0, mipChain);
	CHECK(hr);

	// Create shader resource view with mipmaps
	hr = ::CreateShaderResourceView(DEVICE.Get(),
		mipChain.GetImages(),
		mipChain.GetImageCount(),
		mipChain.GetMetadata(),
		_shaderResourceView.GetAddressOf());
	CHECK(hr);

	// 텍스처 포맷 확인
	DXGI_FORMAT format = md.format;

	_size.x = md.width;
	_size.y = md.height;
}

ComPtr<ID3D11ShaderResourceView> Texture::LoadTextureFromDDS(const wstring& path)
{
	// 파일 확장자 얻기
	wstring ext = fs::path(path).extension();

	DirectX::TexMetadata md;
	DirectX::ScratchImage img;

	HRESULT hr;

	if (ext == L".dds" || ext == L".DDS")
		hr = ::LoadFromDDSFile(path.c_str(), DDS_FLAGS_NONE, &md, img);
	else if (ext == L".tga" || ext == L".TGA")
		hr = ::LoadFromTGAFile(path.c_str(), &md, img);
	else // png, jpg, jpeg, bmp
		hr = ::LoadFromWICFile(path.c_str(), WIC_FLAGS_NONE, &md, img);

	CHECK(hr);

	hr = ::CreateShaderResourceView(DEVICE.Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView.GetAddressOf());
	
	_size.x = md.width;
	_size.y = md.height;

	CHECK(hr);

	return _shaderResourceView;
}

Scene에 이미지 생성

GUIManager는 프로젝트 창에서 이미지 파일을 씬 뷰로 드래그 드롭할 때 이미지의 비율을 유지하면서 UI 컴포넌트를 생성합니다:

void GUIManager::OnResourceDragEnd()
{
    if (!_droppedTexturePath.empty())
    {
        // 경로에서 폴더명 추출
        const char* start = strstr(_droppedTexturePath.c_str(), "Resource/");
        if (!start) return;

        start += strlen("Resource/");
        string folderName = string(start, strcspn(start, "\\"));

        if (folderName == "Texture")
        {
            // XML 파일에서 텍스처 이름 추출
            wstring textureName;
            tinyxml2::XMLDocument doc;
            if (doc.LoadFile(_droppedTexturePath.c_str()) == tinyxml2::XML_SUCCESS)
            {
                if (auto root = doc.FirstChildElement("Texture"))
                {
                    if (auto nameElem = root->FirstChildElement("Name"))
                    {
                        textureName = Utils::ToWString(nameElem->GetText());
                    }
                }
            }

            // UI 오브젝트 생성 및 설정
            wstring sceneName = SCENE.GetActiveScene()->GetSceneName();
            SCENE.SaveAndLoadGameObjectToXML(sceneName, textureName, Vec3::Zero, Vec3::Zero, Vec3::One);
            auto gameObject = SCENE.GetActiveScene()->Find(textureName);
            gameObject->SetObjectType(GameObjectType::UIObject);

            // Material 생성
            MaterialDesc matDesc;
            matDesc.ambient = Vec4(255.0f, 255.0f, 255.0f, 255.0f);
            matDesc.diffuse = Vec4(255.0f, 255.0f, 255.0f, 255.0f);
            matDesc.specular = Vec4(255.0f, 255.0f, 255.0f, 255.0f);

            wstring materialName = textureName + L"_Material";
            wstring materialPath = L"Resource/Material/" + materialName + L".xml";

            RESOURCE.WriteMaterialToXML(
                textureName,        // texture name
                L"",               // normal map (none)
                matDesc,
                L"Debug_UI_Shader",
                materialName,
                materialPath
            );
            RESOURCE.LoadMaterialData(materialPath);

            // MeshRenderer 컴포넌트 추가
            auto meshRenderer = make_shared<MeshRenderer>();
            meshRenderer->SetMesh(RESOURCE.GetResource<Mesh>(L"Quad"));
            meshRenderer->SetModel(nullptr);
            meshRenderer->SetMaterial(RESOURCE.GetResource<Material>(materialName));
            meshRenderer->SetRasterzierState(D3D11_FILL_SOLID, D3D11_CULL_NONE, false);
            meshRenderer->AddRenderPass();
            meshRenderer->GetRenderPasses()[0]->SetPass(Pass::UI_RENDER);
            meshRenderer->GetRenderPasses()[0]->SetMeshRenderer(meshRenderer);
            meshRenderer->GetRenderPasses()[0]->SetTransform(gameObject->transform());
            meshRenderer->GetRenderPasses()[0]->SetDepthStencilStateType(DSState::UI);

            SCENE.AddComponentToGameObjectAndSaveToXML(sceneName, textureName, meshRenderer,
                materialName, L"Quad");

            // UIImage 컴포넌트 추가
            auto uiImage = make_shared<UIImage>();
            Vec2 imageSize = RESOURCE.GetResource<Material>(materialName)->GetTexture()->GetSize() * 0.09765625f;

            POINT mousePos = INPUT.GetPublicMousePos();
            mousePos.x -= GP.GetProjectWidth() * (1.0f / 10.0f);
            mousePos.y -= GP.GetProjectHeight() * (3.0f / 100.0f);
            uiImage->SetTransformAndRect(Vec2(mousePos.x, mousePos.y), imageSize);
            SCENE.AddComponentToGameObjectAndSaveToXML(sceneName, textureName, uiImage);

            // Transform 업데이트
            gameObject->transform()->SetLocalPosition(uiImage->GetNDCPosition());
            gameObject->transform()->SetLocalScale(uiImage->GetSize());
            SCENE.UpdateGameObjectTransformInXML(sceneName, textureName,
                uiImage->GetNDCPosition(), Vec3::Zero, uiImage->GetSize());
        }

        _droppedTexturePath.clear();  // 경로 초기화

        RENDER.GetRenderableObject();
    }
    if (_droppedObject)
    {
        // 최종 위치로 XML 업데이트
        SCENE.UpdateGameObjectTransformInXML(
            SCENE.GetActiveScene()->GetSceneName(),
            _droppedObject->GetName(),
            _droppedObject->transform()->GetLocalPosition(),
            _droppedObject->transform()->GetLocalRotation(),
            _droppedObject->transform()->GetLocalScale()
        );

        _droppedObject = nullptr;
    }
}

이 코드는 다음 단계로 작동합니다:

  1. 드롭된 파일이 이미지인지 확인합니다.

  2. 새 GameObject와 필요한 컴포넌트(UIImage, MeshRenderer)를 생성합니다.

  3. 이미지의 원본 비율을 계산하고 이에 맞게 UI 크기를 조정합니다.

  4. 마우스 위치에 UI 요소를 배치합니다.

  5. 적절한 렌더 패스를 설정하고 씬에 GameObject를 추가합니다.

  6. XML에 GameObject 상태를 저장합니다.

Button 컴포넌트

Button은 UIImage의 기능을 확장하여 클릭 이벤트를 처리할 수 있는 상호작용이가능한 UI 요소입니다. 함수 포인터를 저장하고 클릭 시 해당 함수를 호출합니다.

// Button.h
class Button : public Component
{
    using Super = Component;
public:
    Button();
    virtual ~Button();
    bool Picked(POINT screenPos);
    void AddOnClickedEvent(const string& functionKey);
    void InvokeOnClicked();
    // ... 기타 메서드들 ...
private:
    string _onClickedFunctionKey;  // 클릭 시 호출될 함수 키
    Vec3 _size;                   // 버튼 크기
    Vec3 _ndcPos;                 // 정규화된 장치 좌표계에서의 위치
    RECT _rect;                   // 스크린 좌표계에서의 영역
};

  • Button 이벤트 시스템

MethodRegistry 클래스를 통해 문자열 키로 함수를 등록하고 조회할 수 있는 시스템을 제공합니다:

// MethodRegistry.h
class MethodRegistry
{
public:
    using MethodType = std::function<void(MonoBehaviour*)>;
    
    void registerMethod(const std::string& key, MethodType method) {
        _methods[key] = method;
    }
    
    MethodType getMethod(const std::string& key) {
        auto it = _methods.find(key);
        if (it != _methods.end()) {
            return it->second;
        }
        return nullptr;
    }
    
    const std::unordered_map<std::string, MethodType>& GetAllMethods() const {
        return _methods;
    }
    
private:
    std::unordered_map<std::string, MethodType> _methods;
};

  • REGISTER_MONOBEHAVIOR_METHOD 매크로

스크립트에서 함수를 등록하기 위한 매크로가 제공됩니다:

// MethodRegisterMacro.h
#define REGISTER_MONOBEHAVIOR_METHOD(ClassType, MethodName) \
    static bool s_register_##ClassType##_##MethodName = [](){ \
        std::string className = typeid(ClassType).name(); \
        std::string key = className + "::" + #MethodName; \
        MR.registerMethod(key, \
            [](MonoBehaviour* obj){ \
                auto derived = static_cast<ClassType*>(obj); \
                derived->MethodName(); \
            } \
        ); \
        return true; \
    }();

이 매크로는 클래스와 메서드 이름을 조합하여 고유한 키를 생성하고, 해당 메서드를 실행할 수 있는 함수 객체를 등록합니다.

  • Button에서의 이벤트 처리

Button 클래스는 이벤트 함수를 설정하고 호출하는 메서드를 제공합니다:

// Button.cpp
void Button::AddOnClickedEvent(const string& functionKey, MonoBehaviour* script)
{
    _onClickedFunctionKey = functionKey;
    _onClickedScript = script;  // 스크립트 객체 저장
}

void Button::InvokeOnClicked()
{
    if (!_onClickedFunctionKey.empty())
    {
        if (auto method = MR.getMethod(_onClickedFunctionKey))
        {
            method(_onClickedScript);  // 저장된 스크립트 객체 전달
        }
    }
}

  • 사용 예시

MonoBehaviour를 상속받은 스크립트에서 다음과 같이 버튼 이벤트를 등록할 수 있습니다:

// GUIManager.cpp
// 클릭 이벤트 함수 설정
if (const char* functionKey = buttonElem->Attribute("onClickedFunctionKey")
{
	ButtonEventLoadData eventData;
	eventData.gameObject = gameObj;
	eventData.button = button;
	eventData.functionKey = functionKey;
	buttonEventLoadDataList.push_back(eventData);
}

for (const auto& eventData : buttonEventLoadDataList)
{
	string functionKey = eventData.functionKey;
	if (functionKey.empty()) continue;

	auto gameObj = eventData.gameObject;
	MonoBehaviour* scriptObj = nullptr;

	// 모든 등록된 메서드와 게임 오브젝트의 모든 스크립트 확인
	const auto& registeredMethods = MR.GetAllMethods();

	for (auto& comp : gameObj->GetComponents())
	{
		MonoBehaviour* mb = dynamic_cast<MonoBehaviour*>(comp.get());
		if (!mb) continue;

		// 컴포넌트 클래스 이름 출력
		string compClassName = typeid(*mb).name();

		// 모든 스크립트에서 호출 가능한 함수 리스트 구성
		for (const auto& [key, method] : registeredMethods)
		{

			// 함수 키 비교 - 정확한 문자열 매칭
			if (key == functionKey)
			{
				scriptObj = mb;
				break;
			}

			// 함수 키 비교 - 부분 문자열 매칭
			size_t pos1 = key.find("::");
			size_t pos2 = functionKey.find("::");

			if (pos1 != string::npos && pos2 != string::npos)
			{
				string methodName1 = key.substr(pos1 + 2);
				string methodName2 = functionKey.substr(pos2 + 2);

				if (methodName1 == methodName2)
				{
					scriptObj = mb;
					break;
				}
			}
		}

		if (scriptObj) break;
	}

	// 버튼에 이벤트 설정
	eventData.button->AddOnClickedEvent(functionKey, scriptObj);

Last updated