공부중

[UE] Web Browser Plugin 에서 언리얼 함수를, 언리얼에서 웹페이지함수를 호출해보자. 본문

Programing/UnrealEngine

[UE] Web Browser Plugin 에서 언리얼 함수를, 언리얼에서 웹페이지함수를 호출해보자.

곤란 2023. 11. 28. 18:13
반응형

https://hannom.tistory.com/229

 

[UE]Web Browser Plugin을 이용해 웹페이지를 띄워보자.

언리얼에서 제공해주는 플러그인을 가지고 웹페이지를 띄워보자. 우선 기본 플러그인이 아니기 때문에 플러그인을 아래와 같이 추가해준다. '편집' -> '플러그인' 클릭 'Web Browser'를 체크해준뒤

hannom.tistory.com

 
이전글에서 Web Browser Plugin을 가지고 웹페이지를 볼 수 있도록 했었다.
이제 웹에서 UE함수를... UE에서 Web 함수를 호출하는 방법에 대해서 알아보려고 한다.
 
일단 WebBrowser 클래스를 상속받아서 상세 내용을 구현해야한다.

Widget의 자식인 WebBrowser를 상속받는 클래스를 만들어준다 이름은 자유롭게 짓되 이 글에서는 그냥 MyWebBrowser라고 명명했다.
일단 WebBrowser의 코드를 조금 보면.

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "Components/Widget.h"

#include "WebBrowser.generated.h"

enum class EWebBrowserConsoleLogSeverity;

/**
 * 
 */
UCLASS()
class WEBBROWSERWIDGET_API UWebBrowser : public UWidget
{
	GENERATED_UCLASS_BODY()

	/// 생략...
protected:
	TSharedPtr<class SWebBrowser> WebBrowserWidget;

protected:
	// UWidget interface
	virtual TSharedRef<SWidget> RebuildWidget() override;
	// End of UWidget interface

	/// 생략...
};

WebBrowserWidget이라는 멤버가 있고 그리고 override로 RebuildWidget()이 있다.
RebuildWidget을 보면.

TSharedRef<SWidget> UWebBrowser::RebuildWidget()
{
	if ( IsDesignTime() )
	{
		return SNew(SBox)
			.HAlign(HAlign_Center)
			.VAlign(VAlign_Center)
			[
				SNew(STextBlock)
				.Text(LOCTEXT("Web Browser", "Web Browser"))
			];
	}
	else
	{
		WebBrowserWidget = SNew(SWebBrowser)
			.InitialURL(InitialURL)
			.ShowControls(false)
			.SupportsTransparency(bSupportsTransparency)
			.OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleOnUrlChanged))
			.OnBeforePopup(BIND_UOBJECT_DELEGATE(FOnBeforePopupDelegate, HandleOnBeforePopup))
			.OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage));

		return WebBrowserWidget.ToSharedRef();
	}
}

WebBrowserWidget을 생성해주고 있는 코드가 있다. 추가로 DELEGATE Bind해주고 있는게 보인다.
 
좀 더 내부를 들어가 SWebBrowser를 살펴보면..

class SWebBrowser
	: public SCompoundWidget
{
	/// 생략.

	SLATE_BEGIN_ARGS(SWebBrowser)
		: _InitialURL(TEXT("https://www.google.com"))
		, _ShowControls(true)
		, _ShowAddressBar(false)
		, _ShowErrorMessage(true)
		, _SupportsTransparency(false)
		, _SupportsThumbMouseButtonNavigation(true)
		, _ShowInitialThrobber(true)
		, _BackgroundColor(255,255,255,255)
		, _BrowserFrameRate(24)
		, _PopupMenuMethod(TOptional<EPopupMethod>())
		, _ViewportSize(FVector2D::ZeroVector)
	{ }

		/** A reference to the parent window. */
		SLATE_ARGUMENT(TSharedPtr<SWindow>, ParentWindow)

		/** URL that the browser will initially navigate to. The URL should include the protocol, eg http:// */
		SLATE_ARGUMENT(FString, InitialURL)

		/** Optional string to load contents as a web page. */
		SLATE_ARGUMENT(TOptional<FString>, ContentsToLoad)

		/** Whether to show standard controls like Back, Forward, Reload etc. */
		SLATE_ARGUMENT(bool, ShowControls)

		/** Whether to show an address bar. */
		SLATE_ARGUMENT(bool, ShowAddressBar)

		/** Whether to show an error message in case of loading errors. */
		SLATE_ARGUMENT(bool, ShowErrorMessage)

		/** Should this browser window support transparency. */
		SLATE_ARGUMENT(bool, SupportsTransparency)

		/** Whether to allow forward and back navigation via the mouse thumb buttons. */
		SLATE_ARGUMENT(bool, SupportsThumbMouseButtonNavigation)

		/** Whether to show a throbber overlay during browser initialization. */
		SLATE_ARGUMENT(bool, ShowInitialThrobber)

		/** Opaque background color used before a document is loaded and when no document color is specified. */
		SLATE_ARGUMENT(FColor, BackgroundColor)

		/** The frames per second rate that the browser will attempt to use. */
		SLATE_ARGUMENT(int , BrowserFrameRate)

		/** Override the popup menu method used for popup menus. If not set, parent widgets will be queried instead. */
		SLATE_ARGUMENT(TOptional<EPopupMethod>, PopupMenuMethod)

		/** Desired size of the web browser viewport. */
		SLATE_ATTRIBUTE(FVector2D, ViewportSize);

		/** Called when document loading completed. */
		SLATE_EVENT(FSimpleDelegate, OnLoadCompleted)

		/** Called when document loading failed. */
		SLATE_EVENT(FSimpleDelegate, OnLoadError)

		/** Called when document loading started. */
		SLATE_EVENT(FSimpleDelegate, OnLoadStarted)

		/** Called when document title changed. */
		SLATE_EVENT(FOnTextChanged, OnTitleChanged)

		/** Called when the Url changes. */
		SLATE_EVENT(FOnTextChanged, OnUrlChanged)

		/** Called before a popup window happens */
		SLATE_EVENT(FOnBeforePopupDelegate, OnBeforePopup)

		/** Called when the browser requests the creation of a new window */
		SLATE_EVENT(FOnCreateWindowDelegate, OnCreateWindow)

		/** Called when a browser window close event is detected */
		SLATE_EVENT(FOnCloseWindowDelegate, OnCloseWindow)

		/** Called before browser navigation. */
		SLATE_EVENT(FOnBeforeBrowse, OnBeforeNavigation)

		/** Called to allow bypassing page content on load. */
		SLATE_EVENT(FOnLoadUrl, OnLoadUrl)

		/** Called when the browser needs to show a dialog to the user. */
		SLATE_EVENT(FOnShowDialog, OnShowDialog)

		/** Called to dismiss any dialogs shown via OnShowDialog. */
		SLATE_EVENT(FSimpleDelegate, OnDismissAllDialogs)

		SLATE_EVENT(FOnSuppressContextMenu, OnSuppressContextMenu);

		/** Called when drag is detected in a web page area tagged as a drag region. */
		SLATE_EVENT(FOnDragWindow, OnDragWindow);

		/** Called for each console message */
		SLATE_EVENT(FOnConsoleMessageDelegate, OnConsoleMessage);

	SLATE_END_ARGS()

	/// 생략.

	/** Execute javascript on the current window */
	WEBBROWSER_API void ExecuteJavascript(const FString& ScriptText);

	/**
	 * Gets the source of the main frame as raw HTML.
	 *
	 * This method has to be called asynchronously by passing a callback function, which will be called at a later point when the
	 * result is ready.
	 * @param	Callback	A callable that takes a single string reference for handling the result.
	 */
	WEBBROWSER_API void GetSource(TFunction<void (const FString&)> Callback) const;

	/**
	 * Expose a UObject instance to the browser runtime.
	 * Properties and Functions will be accessible from JavaScript side.
	 * As all communication with the rendering procesis asynchronous, return values (both for properties and function results) are wrapped into JS Future objects.
	 *
	 * @param Name The name of the object. The object will show up as window.ue.{Name} on the javascript side. Note: All object names, function names, and property names will be converted to lower case when bound on the javascript side.  If there is an existing object of the same name, this object will replace it. If bIsPermanent is false and there is an existing permanent binding, the permanent binding will be restored when the temporary one is removed.
	 * @param Object The object instance.
	 * @param bIsPermanent If true, the object will be visible to all pages loaded through this browser widget, otherwise, it will be deleted when navigating away from the current page. Non-permanent bindings should be registered from inside an OnLoadStarted event handler in order to be available before JS code starts loading.
	 */
	WEBBROWSER_API void BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true);

	/**
	 * Remove an existing script binding registered by BindUObject.
	 *
	 * @param Name The name of the object to remove.
	 * @param Object The object will only be removed if it is the same object as the one passed in.
	 * @param bIsPermanent Must match the bIsPermanent argument passed to BindUObject.
	 */
	WEBBROWSER_API void UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true);

	/// 생략
    
};

Bind관련 함수도 보이고 JS를 실행하수 있는 함수도 보인다. 이것을 이용하면 적절하게 실행이 가능할것 같다.
 
아무튼 WebBrowser를 상속받아서 MyWebBrowser를 아래와 같이 구현했다.

#pragma once

#include "CoreMinimal.h"
#include "WebBrowser.h"
#include "MyWebBrowser.generated.h"

/**
 * 
 */
UCLASS()
class WEBBROWSERTEST_API UMyWebBrowser : public UWebBrowser
{
	GENERATED_BODY()

	DECLARE_DELEGATE(FOnLoadCompletedDelegate);

public:

	UFUNCTION(BlueprintCallable)
	void		TestPrintFunction(const FString& msg);

protected:
	// UWidget interface
	virtual TSharedRef<SWidget> RebuildWidget() override;
	// End of UWidget interface

	void HandleOnLoadCompleted();
	void RecvConsoleMessageFromJS(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity);

};

RebuildWidget을 재정의 할것이고 TestPrintFunction은 JS에서 호출해줄 예정이다.
그리고 HandleOnLoadCompleted와 RecvConsoleMessageFromJS는 Delegate로 등록해줄것이다.
 

#include "MyWebBrowser.h"
#include "SWebBrowser.h"

#define LOCTEXT_NAMESPACE "MyWebBrowser"

void UMyWebBrowser::TestPrintFunction(const FString& msg)
{
	if (GEngine)
	{
		GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, msg);
	}
}

TSharedRef<SWidget> UMyWebBrowser::RebuildWidget()
{
	if (IsDesignTime())
	{
		return SNew(SBox)
			.HAlign(HAlign_Center)
			.VAlign(VAlign_Center)
			[
				SNew(STextBlock)
					.Text(LOCTEXT("Web Browser", "Web Browser"))
			];
	}
	else
	{
		WebBrowserWidget = SNew(SWebBrowser)
			.InitialURL(InitialURL)
			.ShowControls(false)
			.SupportsTransparency(bSupportsTransparency)
			.OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleOnUrlChanged))
			.OnBeforePopup(BIND_UOBJECT_DELEGATE(FOnBeforePopupDelegate, HandleOnBeforePopup))
			.OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, RecvConsoleMessageFromJS))
			.OnLoadCompleted(BIND_UOBJECT_DELEGATE(FOnLoadCompletedDelegate, HandleOnLoadCompleted));
	
		return WebBrowserWidget.ToSharedRef();
	}
}

void UMyWebBrowser::HandleOnLoadCompleted()
{
	if (GEngine)
	{
		GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, TEXT("[UMyWebBrowser::HandleOnLoadCompleted] Call"));
	}

	if (WebBrowserWidget.IsValid())
	{
		WebBrowserWidget->BindUObject(TEXT("MyWebBrowser"), this, true);
		if (GEngine)
		{
			GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, TEXT("[UMyWebBrowser::HandleOnLoadCompleted] BindUObject Call"));
		}
	}

}

void UMyWebBrowser::RecvConsoleMessageFromJS(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity)
{
	if (GEngine)
	{
		GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, Message);
	}

}

UE_LOG대신 뷰에 좀 보이도록 AddOnScreenDebugMessage를 전체적으로 사용했다.
RebuildWidget은 부모의 내용을 그대로 긁어와서 OnConsoleMessage에는 RecvConsoleMessageFromJS를 Bind해주고.
OnLoadCompleted를 추가해서 HandleOnLoadCompleted를 Bind해주었다.
 
HandleOnLoadCompleted에서 BindUObject를 하는데 여기의 주석을 좀 봐야한다.

	/**
	 * Expose a UObject instance to the browser runtime.
	 * Properties and Functions will be accessible from JavaScript side.
	 * As all communication with the rendering procesis asynchronous, return values (both for properties and function results) are wrapped into JS Future objects.
	 *
	 * @param Name The name of the object. The object will show up as window.ue.{Name} on the javascript side. Note: All object names, function names, and property names will be converted to lower case when bound on the javascript side.  If there is an existing object of the same name, this object will replace it. If bIsPermanent is false and there is an existing permanent binding, the permanent binding will be restored when the temporary one is removed.
	 * @param Object The object instance.
	 * @param bIsPermanent If true, the object will be visible to all pages loaded through this browser widget, otherwise, it will be deleted when navigating away from the current page. Non-permanent bindings should be registered from inside an OnLoadStarted event handler in order to be available before JS code starts loading.
	 */
	WEBBROWSER_API void BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true);

* @param Name The name of the object. The object will show up as window.ue.{Name} on the javascript side. Note: All object names, function names, and property names will be converted to lower case when bound on the javascript side.
여기서 전달해주는 Name을 가지고 JS에서 window.ue.{name} 형태로 접근이 가능해지고
object, function, property 모두 소문자로 써야한다.
 
나머지는 직접 읽어보고 적절하게 사용하면 된다.
내 코드가 항상 정답이고 정답이다는 아니기 때문에 참고만 바랍니다
 
여기에 맞게 수정된 HTML과 JS는 아래와 같다.

<!DOCTYPE html>
<head>
    <script src="index.js"></script>
</head>
<body>
    <div>인덱스 페이지</div>
    <div class="TestClass">0</div>
    <button onclick="OnClickBtn()">버튼</button>
    <button onclick="OnClickBtnCallUnrealFunction()">Unreal함수 호출 버튼</button>
</body>
var GlobalValue = 0;

function OnClickBtn()
{
    document.getElementsByClassName('TestClass')[0].innerText = ++GlobalValue;
}

function OnClickBtnCallUnrealFunction()
{
    window.ue.mywebbrowser.testprintfunction("Call From JS, GlocalValue : " + GlobalValue);
}

window.onload = function(){
    console.log('onload call');
}

Unreal에서 호출해줄 함수는 OnClickBtnCallUnrealFunction이고 내부를 보면 위에서 언급했듯이 모두 소문자로 썼다.
 
그리고 이제 위젯 Blueprint에서도 적절하게 수정해 준다.

상속받아서 구현한 MyWebBrowser로 변경했고 Unreal에서 JS함수를 호출하기 위한 버튼하나를 만들었다.

간단하게 보여줄것이기 때문에 굳이 코드로는 하지 않고 BP 노드로 작성했다.
필요하면 적절하게 코드로 변환해서 쓰면 된다.
 
결과는 아래와 같다.
https://youtu.be/bXqQvbzMCQk

 

반응형